设计模式之 六大原则

一.单一职责

定义: 一个类承担的职责不宜过多,或者说就一个类而言,应该仅有一个引起它变化的原因

如果一个类的职责承担过多,如果涉及到其中每一个职责变动的时候,都要修改这个类,而且在我们要复用这个类中的其中一个职责的时候也没法做到复用。

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科,部分文理,这样造成喜欢文科的也不得不考理科,喜欢理科的不得不考文科,上海表示抗议,要分文理科,于是上海私自做决定, 考试我还是考(实现全国卷统一接口),但是文科生理综不计入总分(空方法实现),理科生分科不计入总分(空方法实现), 接着北京,天津等直辖市纷纷效仿,深圳广州等城市还是按照综合考试,觉得这样有利于学生综合能力发展,这时候教育部想了个办法把学科分成主课+文+理(三个接口,这样做到了接口最小化), 愿意分文理就靠文科的,愿意综合的就靠所有的,这样大家都没意见了。

 

如果某些时候遵守规则让你开发效率更低,那还不如违反规则以得到更好的开发效率,规则是死的,人是活的,我们开发的时候尽量遵守这些规则即可

 

 

posted @ 2017-09-28 19:54  gaoqin31  阅读(286)  评论(0编辑  收藏  举报