设计模式之 六大原则
一.单一职责
定义: 一个类承担的职责不宜过多,或者说就一个类而言,应该仅有一个引起它变化的原因
如果一个类的职责承担过多,如果涉及到其中每一个职责变动的时候,都要修改这个类,而且在我们要复用这个类中的其中一个职责的时候也没法做到复用。
class Act{ public function run(){ $data = $this->curl($url); } public function curl($url, $data = array(), $timeout = 5) { $ch = curl_init(); if (!empty($data) && $data) { if(is_array($data)){ $formdata = http_build_query($data); } else { $formdata = $data; } curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, $formdata); } curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout); curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); curl_setopt($ch, CURLOPT_ENCODING, ''); $result = curl_exec($ch); curl_close($ch); return $result; } }
看到项目里经常有把请求第三方接口的curl方法写在当前类里,比如上面的活动类Act耦合了curl方法,如果此时又有另外一个类要使用curl方法,这个类并不需要活动类的其它功能此时就不能复用Act的curl方法
只能在这个类里在增加一个curl方法,如此下去,随着类越来越多,项目里到处是curl方法,假如某一天需要把超时改成10秒,那就惨了,得一个个类里去修改。其实我们完全可以把curl方法放到curl类里,将Act类和curl解耦,
这样项目统一使用curl类的curl方法,新增功能的时候可复用Curl的curl方法,修改的时候只需修改Curl类。
class Act{ public function run(){ $data = Curl::curl($url); } } class Curl{ public static function curl($url, $data = array(), $timeout = 5) { /***代码略***/ } }
二.开放封闭原则
定义: 软件实体(类,函数模块) 应该是可以扩展的,但是不可以修改
这应该是软件开发的理想境界,其实我们不可能做到完全对修改关闭,只是尽量做到即可。
比如游戏里的支付回调,可以抽象为3个步骤,验证第三方参数,发道具给玩家,返回报文给第三方。
abstract class Notify{ //回调状态码 protected $_status = 0; protected abstract function checkSign(); protected abstract function SendProp(); protected abstract function OutputMessage(); public final function run(){ if($this->checkSign()){ if($this->SendProp()){ //成功写入数据记录 $this->_writeDBLog(); $this->_status = 1; }else{ $this->_status = -1;//发道具错误 } }else{ $this->_status = 2;//第三方参数错误 } $this->OutputMessage(); } private function _writeDBLog(){ echo "写入DB日志\n"; } } class Weixin extends Notify{ protected function checkSign(){ return true; } protected function SendProp(){ return true; } protected function OutputMessage(){ if($this->_status == 1){ die('ok'); }else{ die('fail'); } } } $weixin = new Weixin(); $weixin ->run();
上面是一个第三方回调的主体代码,我抽掉了其他的业务逻辑,每个支付子类继承子一个抽象父类,这样做的好处是可以把公共的逻辑放到父类里,无需每个子类重复写公共逻辑,假如要新增支付宝,
只需要新增一个支付宝类,然后实现子类特有的业务逻辑即可。当然如果某天要新增文件日志,那必须改父类了,团队里开发人员水平参差不齐,我们可以把 Notify父类这个文件设置为指定人员可改。
这样基本做到了对扩展开防,对修改关闭。
三.里氏替换原则
定义: 子类型必须能够替换掉他们的父类型
意思是尽量不要重写父类的方法,因为父类的方法一般是公用的
由于世界上最好的语言是弱类型,这个原则PHP实在无法理解,这里使用java来理解这个原则
class Car{ String carType; public Car(String carType){ this.carType = carType; } public void run(){ System.out.println(carType + "跑"); } } class HondaCar extends Car{ public HondaCar(String carType){ super(carType); } /* public void run(){ throw new Exception(); } */ } class FordCar extends Car{ public FordCar(String carType){ super(carType); } } public class Client { public static void main(String[]agrs){ Car h = new HondaCar("丰田"); h.run(); FordCar f = new FordCar("福特"); f.run(); } }
子类型FordCar f对象,可以替换掉父类型Car h对象,但假如 HondaCar类重写父类的run方法,显然替换会造成程序异常。
然而有的时候我们不得不重写父类的方法,只要重写父类方法能给我们带来大利大于弊的时候即可。
四.依赖倒转原则
定义 : 抽象不应该依赖于细节,细节应该依赖于抽象,要针对接口编程而不是对具体实现编程
这个原则还是用java来理解
class Members{ public void set(){ Redis redis = new Redis(); redis.set("u_key", "u_value"); } } class Redis{ public void set(String key, String val){ System.out.println("Redis设置key:" + key + "value :" + val + "成功!"); } } public class Client { public static void main(String[]agrs){ Members m = new Members(); m.set(); } }
假如存贮换为Mysql那么我们得修改Members类,我们改下代码,增加一个Istore接口,Redis和Mysql类实现set方法。具体的类名我们可以写到配置文件里,然后通过反射实例化相应的对象,
换存贮只需要修改配置文件,增加对应的存储类即可做到符合开闭原则。
interface Istore{ public void set(String key, String val); } class Members{ public void set(){ Istore store = new Mysql(); store.set("u_key", "u_value"); } } class Mysql implements Istore{ public void set(String key, String val){ System.out.println("Mysql设置key:" + key + "value :" + val + "成功!"); } } class Redis implements Istore{ public void set(String key, String val){ System.out.println("Redis设置key:" + key + "value :" + val + "成功!"); } } public class Client { public static void main(String[]agrs){ Members m = new Members(); m.set(); } }
五.迪米特法则
定义 : 一个类应该尽量降低自己成员的访问权限,如果两个类不必发生直接通讯,那么这两个类就不应该直接发生相互作用,如果其中一个类要调用另外一个类的方法,可以通过第三者转发这个调用
这个原则强调尽量降低类和类之间的耦合度,一个处于松耦合的类一旦被修改,不会对关联的类造成太大的影响
class Cache{ function getResult(){ return array('id'=>1, 'name'=>'PHP'); } } class Mysql{ function getResult(){ return array('id'=>1, 'name'=>'PHP'); } } class Client{ public static function main(){ $data = array(); $cache = new Cache(); if(!$data = $cache->getResult()){ $db = new Mysql(); $data = $db->getResult(); } print_r($data); } } Client::main();
上面这个是我们经常碰到的业务场景,先从cache里取数据,取不到就从db里取,万一要加个文件里取,就得修改客户端配置,如果调用的地方很多,每个地方都得改。
此时根据迪米特法则完全可以引入一个对象当中间人,使得客户端不用直接和db,cache交互。
class Cache{ function getResult(){ return array('id'=>1, 'name'=>'PHP'); } } class Mysql{ function getResult(){ return array('id'=>1, 'name'=>'PHP'); } } class Proxy{ static function getResult(){ $data = array(); $cache = new Cache(); if(!$data = $cache->getResult()){ $db = new Mysql(); $data = $db->getResult(); } return $data; } } class Client{ public static function main(){ $data = Proxy::getResult(); print_r($data); } } Client::main();
六.接口隔离原则
定义 : 不要建立庞大臃肿的接口,接口中的方法尽量少
如果违反这一原则,有时候不需要这些方法子类也不得不实现这个方法。
下面以高考作为一个栗子(纯属虚构), 最初教育部规定各省市考试有7科,部分文理,这样造成喜欢文科的也不得不考理科,喜欢理科的不得不考文科,上海表示抗议,要分文理科,于是上海私自做决定, 考试我还是考(实现全国卷统一接口),但是文科生理综不计入总分(空方法实现),理科生分科不计入总分(空方法实现), 接着北京,天津等直辖市纷纷效仿,深圳广州等城市还是按照综合考试,觉得这样有利于学生综合能力发展,这时候教育部想了个办法把学科分成主课+文+理(三个接口,这样做到了接口最小化), 愿意分文理就靠文科的,愿意综合的就靠所有的,这样大家都没意见了。
如果某些时候遵守规则让你开发效率更低,那还不如违反规则以得到更好的开发效率,规则是死的,人是活的,我们开发的时候尽量遵守这些规则即可