说说自己工作中天天使用的设计模式

4,941 阅读9分钟
原文链接: mp.weixin.qq.com

设计模式,听起来似乎很高大上,实际也的确很高大上,毕竟都是非常有经验的开发人员在实战的开发中总结出来的套路。只要留心,你会发现其实我们天天在用,在享受它给我们带来的好处和便利。对设计模式的理解,需要日益积累的工作经验,只有走了弯路看到直路后才恍然大悟。哦,原来这里如果使用某某模式就更好了!

面向对象的六大设计原则是这样写的:


1、单一职责原则:避免职责分散,避免承担太多(SRP)

2、开闭原则:模块应对扩展开放,而对修改关闭(OCP)。

3、里氏代换原则:子类必须能替换掉父类(LSP)。

4、依赖倒转原则:父类不依赖子类,抽象不依赖具体(DIP)

5、接口隔离原则:职业单一,承诺最简(ISP)

6、组合复用原则:尽量使用组合,避免滥用继承(CRP)


感觉在自己实际工作,在实现第一个原则“开闭原则”的时候就不小心把下面的原则也都实现了。所谓开闭原则,即对扩展开放对修改关闭。我个人简单的理解,新增的功能需要,就写到扩展里面,不要在原来的代码里去修改了。这样说,可能比较隐晦,等会看代码就一目了然了。
(桶哥注:就是常说的非侵入式写代码)


谈设计模式,第一个,不解释肯定是工厂模式了,别问我为什么,就是酱紫,就像 hello world 一样。以一个用户类为例:


//会员系统

class Member{

    private $type;

    public function __construct( $type ){ 

        $this->type = $type;

    }

 

    //获取会员可以用积分兑换的商品

    function getFreeProduct(){

        switch ($this->type){

            case 'iron':

                return '铁牌会员;

            case 'copper':

                return '铜牌会员';

            default:

                return '铁牌会员';

        }

    }

 

    //获取会员的权限

    function getPermission(){

        switch ($this->type){

            case 'iron':

                return '铁牌会员';

            case 'copper':

                return '铜牌会员';

            default:

                return '铁牌会员';

        }

    }

}




如果后面我们再新增一种会员类型,如果Member类里不仅仅只有getFreeProductgetPermission这两个方法,

那么就需要在每个方法的switch case里面增加新的分支case,这样的代码就违背咱们开发的第一个原则(开闭原则)了。


打个比方,如果5种会员的功能要更新,任务量实在太大,项目却又急着更新,所以我们会把5种类型会员的功能交给团队里的5个人来实现。比如现在A负责金牌会员,B负责银牌会员...A需要修改金牌会员的优惠规则,他需要改Member类,B也要修改银牌会员的优惠规则,他也需要马上修改Member类。这样的情况就很多了,大家都动这个代码,就很不好管理了,代码覆盖,代码冲突等等。

那么怎解决呢?工厂模式就很好的解决了这个问题:


class MemberFactory{

    private static $memberArr = array('iron', 'copper');

    public static function Create( $type ){

        $type = strtolower(trim($type));

        if (!in_array($type, self::$memberArr)){

            throw new Exception('error member type'); 

        }

        $classMember = ucfirst($type)."Member";

        require 'Member/'.$classMember.'class.php';

        return new $classMember();

    }

}


正如我上面的代码注释中说的,把各个类型的用户类(比如ironMember,copperMember)都统一放到./Mmeber/目录下, 以ironMember.class.phpcopperMember.class.php为文件名。需要实例化的时候就包含该文件,这样也保证了各个开发人员的代码不冲突。后期也易扩展,新增一种类型的用户,只需新增一个类型的文件即可。

这样看我们工作中是不是天天在用工厂模式呢?

(桶哥注:工厂模式基本成为现代开发框架的两个基石之一)


不过有没有发现,如果我们在一个进程里面很多地方调用

MemberFactory::Create()

就会每次都重新实例化一次,这是不是太浪费资源了呢?


这个最常见的就是我们项目里面的数据库连接了,原来我们写面向过程的代码的时候也知道把数据库连接写在最上面,保证一个进程里面只连接一次,然后在页面底部释放连接。


那么现如今,我们以面向对象的方式来开发,如何保证只建立一个连接呢?


这样就要说到单例模式(又是套路,说完工厂说单例)

(桶哥注:单例模式基本成为现代开发框架的另一个基石)


接着上面用户类的实例化的内容继续说。最简单的办法呢就是把已经实例化过的对象放在一个工厂方法里面的静态成员里面,而这个成员是一个数组,把用户类型作为下标,如果在这个进程中之前实例化过就直接返回,没有则实例化并且存入这个静态成员里面,这样说呢,也不是完全的单例模式,但是是单例模式的一个思维方式,也是缓存的一个思维方式。


class MemberFactoryNew{

    private static $memberArr = array('iron', 'copper');

    private static $_instance = array();

    public static function Create( $type ){

        $type = strtolower(trim($type));

        if (!in_array($type, self::$memberArr)){

            throw new Exception('error member type'); 

        }

        

        if (!self::$_instance[$type]){

            $classMember = ucfirst($type)."Member";

                        self::$_instance[$type] = new $classMember();

                }

        return self::$_instance[$type];

    }

}


拿之前我写的那篇ThinkPHP的缓存体系分析就是一个很好的例子

ThinkPHP里有很多缓存扩展



class Cache {

 

    protected $handler;

 

    protected $options = array();

 

    public function connect($type='',$options=array()) {

        if(empty($type))  $type = C('DATA_CACHE_TYPE');

        $type  = strtolower(trim($type));

        $class = 'Cache'.ucwords($type);

        if(class_exists($class))

            $cache = new $class($options);

        else

            throw_exception(L('_CACHE_TYPE_INVALID_').':'.$type);

        return $cache;

    }

 

    public function __get($name) {

        return $this->get($name);

    }

 

    public function __set($name,$value) {

        return $this->set($name,$value);

    }

 

    public function __unset($name) {

        $this->rm($name);

    }

    public function setOptions($name,$value) {

        $this->options[$name]   =   $value;

    }

 

    public function getOptions($name) {

        return $this->options[$name];

    }

 

    static function getInstance() {

       $param = func_get_args();

        return get_instance_of(__CLASS__,'connect',$param);

    }

 

    //队列缓存,下篇笔记

    protected function queue($key) {

        

    }

    

    public function __call($method,$args){

        //调用缓存类型自己的方法

        if(method_exists($this->handler, $method)){

           return call_user_func_array(array($this->handler,$method), $args);

        }else{

            throw_exception(__CLASS__.':'.$method.L('_METHOD_NOT_EXIST_'));

            return;

        }

    }

}



Cache类里通过connect方法实际就是一个工厂方法,实现了各个 Cache 子类的实例化,就和上面的各个用户子类的实例化一样。



具体的ThinkPHP的整个缓存体系是怎么走的,这里大体说下流程:


1、S->Cache::getInstance($type,$options)

2、get_instance_of

3、call_user_func_array(array('Cache', 'connect'), $args)


也许你和我一样看到这个Cache:connect方法还在想刚刚不是说只实例化一次么?这里怎么没有那样做呢?这也是ThinkPHP的高明之处,他把这个工厂方法又抽象了一层,都写到了get_instance_of这个函数里,在实例化对象的时候先找这个函数调,静态变量里没有再去找工厂方法。


/**

 * 取得对象实例 支持调用类的静态方法

 * @param string $name 类名

 * @param string $method 方法名,如果为空则返回实例化对象

 * @param array $args 调用参数

 * @return object

 */

function get_instance_of($name, $method='', $args=array()) {

    static $_instance = array();

    $identify = empty($args) ? $name . $method : $name . $method . to_guid_string($args);

    if (!isset($_instance[$identify])) {

        if (class_exists($name)) {

            $o = new $name();

            if (method_exists($o, $method)) {

                if (!empty($args)) {

                    $_instance[$identify] = call_user_func_array(array(&$o, $method), $args);

                } else {

                    $_instance[$identify] = $o->$method();

                }

            }else{

                $_instance[$identify] = $o;

            }

        }else{

            halt(L('_CLASS_NOT_EXIST_') . ':' . $name);        

        }

    }

    return $_instance[$identify];

}



如果没有这个专门用来实例化对象的函数,那么我们在我们的Cache工厂方法,DB工厂方法,Member工厂方法里都要去做单例的处理,这样是不是代码重复得太多了呢?如果把单例处理抽出来,而工厂只负责创造对象而不去负责是否单例判断。


继续说上面的Cache类,其实这里还是运用了"适配器模式"的思想。不管你是memcache还是apc或者是Xcache,在这些子类里面都统一把缓存的CURD封装在了get,set,rm方法里面,每个缓存的子类都严格按照这个要求来,这样我们都可以通过Cache类就可以统一调用这些方法了,而不用管各个不通缓存具体实现时使用的具体函数了。这就是“适配器模式”。这里Cache类不是严格的适配器模式,可以简单参考下下面的代码:


interface CacheInterface

{

    function set($name, $value, $expire = null);

 

    function get($name);

 

    function delete($name);

}

 

//适配器

class CacheApc implements CacheInterface

{

    function get($name) {

        return apc_fetch($name);

    }

 

    function set($name, $value, $expire = null) {

        return apc_store($name, $value, $expire);

    }

 

    function delete($name) {

         return apc_delete($name);

    }

}

 

class CacheMemcache implements CacheInterface

{

    private $memcache_obj;

 

    function __construct() 

    {

        $this->memcache_obj = new Memcache;

        $this->memcache_obj->connect('localhost', 11211);

    }

 

    function get($name) {

        return $this->memcache_obj->get($name);

    }

 

    function set($name, $value, $expire = null) {

        return $this->memcache_obj->set($name, $value, $expire);

    }

 

    function delete($name) {

         return $this->memcache_obj->delete($name);

    }

}

 

//把工厂模式应用进来

class Cache

{

    private static $cacheArr = array('apc', 'memcache');

    private static $cacheed = array();

    public static function getInstance( $type )

    {

        $type = strtolower(trim($type));

        if (!in_array($type, self::$cacheArr))

        {

            throw new Exception('error cache type'); 

        }

        //这里也可以用switch case来创建对象,可读性更好

        

        if(!Cache::cached[$type]){

            $class = "Cache".ucfirst($type);

            Cache::cached[$type] = new $class;

        }

        return Cache::cached[$type] ;

    }

}


看完以后是不是想说:“尼玛,我不是天天就这么在用么?”


而所谓的装饰器模式的使用范围则更加广泛,只是我们在工作偷懒了,都用简单的if else完成了,装饰器嘛,就是点缀的意思,比如给某个帖子后面加个精华的按钮,给某个商品的后面添加个促销的提示。这就是装饰,就像现在的美女都喜欢带个兔女郎发卡。为什么要使用装饰模式呢?也就是为了遵循开闭原则,新增的需求都在扩展中写。


而建造者模式,则更加不用说了,面向过程的时候就天天在用,一个函数,一个方法一般都隐性默认为只做一件事,那么很多逻辑复杂的事情就要抽出来重新封装成一个方法或者函数,


这就是建造者模式了。。。被自己萌哭了。


后面还有很多模式,比如访问者模式,状态模式,组合模式,桥接模式等,我们实际工作也经常在使用,只是自己没太注意。后续会陆续更新相关模式文章。


(桶哥注:单例模式一般都可以升级为全局注册树来进行单例)


--------------伟大的分割线----------------

PHP饭米粒(phpfamily) 由一群靠谱的人建立,愿为PHPer带来一些值得细细品味的精神食粮!

饭米粒只发原创或授权发表的文章,不转载网上的文章

所发的文章,均可找到原作者进行沟通。

也希望各位多多打赏(算作稿费给文章作者),更希望大家多多投搞。

投稿请联系:

shenzhe163@gmail.com


本文由 周梦康 独家授权 php饭米粒发布,转载请注明本来源信息和以下的二维码(长按可识别二维码关注)