策略模式初探

什么是策略模式?

定义一系列的算法,把它们一个个封装起来,并且使它们可互相替换。 《设计模式》

定义了一族算法(业务规则);
封装了每个算法;
这族的算法可互换代替(interchangeable)。 维基百科

完全无法理解有木有?

  • 看一个例子:

比如说我,

我住在公司附近。

骑单车要十分钟左右。

走路需要二十分钟左右。

但是我需要根据天气来选择到底是骑单车还是走路。

下雨的话,没办法骑单车只能走路过去了。

晴天就可以睡晚一点骑单车去。

用伪代码来实现就会是这样:

    class way {

        // 1 晴天
        // 2 下雨
        public function go($weather){
            if ( $weather === 1 ) {
                echo '晴天可以骑单车!';
            } else if ( $weather === 2 ) {
                echo '下雨走路!';
            }
        }
    }

但是,如果碰到下冰雹呢,或者台风呢?

OK,很简单,我们再去修改一下代码

 class way {

        // 1 晴天
        // 2 下雨
        // 3 冰雹
        // 4 下雪
        public function go($weather){
            switch ( $weather ) {
                case 1:
                    echo '骑单车';
                    break;
                case 2:
                    echo '走路';
                    break;
                case 3:
                    echo '打的';
                    break;
                case 4:
                    echo '休假';
                    break;
                default:
                    echo '不知道该怎么去';
                    break;
            }
        }
    }
  • 那么问题来了

以后每次增加新的方式都要去对代码进行修改吗?

假设每次都进行修改,那么长久下去,这个方法会变得臃肿不堪,并且不易维护和扩展,不符合我们软件工程的铁律--高内聚,低耦合。

那么,怎么重构它呢?

在前辈们的探索下,总结出了一个方法,这个方法就是我们现在要了解的策略模式。

    interface way {
        public function go();
    }

    //骑单车
    class bike implements way {
        public function go() 
        {
            echo '骑单车';
        }
    }

    //走路
    class walk implements way {
        public function go() 
        {
            echo '走路';
        }
    }

    // 我
    class Person {

        private $way;

        public function __construct (way $way) 
        {
            $this->way = $way;
        }

        public function change_way (way $way) 
        {
            $this->way = $way;
        }

        public function go_company ()
        {
            $this->way->go();
        }
    }

    $me = new Person( new bike() );
    $me->go_company(); // 骑单车

    $me->change_way( new walk() );
    $me->go_company(); //走路

这样一来,当有新的方式出现的时候,只需要增加方法类就可以了,完全不需要改动其他的地方。

  • 回顾一下

我们先设计了一个接口类way,两个去公司的方法类继承这个接口,最后由我来选择用哪个。

  • 看一下定义:

定义一系列的算法,把它们一个个封装起来,并且使它们可互相替换。 《设计模式》

定义了一族算法(业务规则);
封装了每个算法;
这族的算法可互换代替(interchangeable)。 维基百科

从定义中能看出来,他们的意思其实是一样的。

首先要定义一系列或者说一族的算法进行封装。

  • 什么是一系列,一族呢

拥有同一种特性的事物就是一系列或者说一族。

上面我们定义了一个接口类way, 来使继承类必须实现一个相同名字的方法go。 这些类就可以称作为一族,一系列。

这族的算法可互换代替(interchangeable)

用上面的例子来说就是:

我可以随意选择去公司的方式。

  • 再来看一下定义:

定义一系列的算法,把它们一个个封装起来(继承接口way,全都实现go方法),并且使它们可互相替换(我随便用那种方式去公司)。 《设计模式》

定义了一族算法(业务规则);(定义接口类way)
封装了每个算法;(全都实现go方法)
这族的算法可互换代替(interchangeable)。(我随意用那种方法去公司) 维基百科

  • 策略模式的分析

从上面的例子中发现,策略模式只是对算法进行封装(对决定怎么去公司进行封装),把算法和行为分隔开。

具体怎么去使用这些算法是由我决定的(也就是客户端),客户端必须先理解所有的算法之间的区别并且决定使用哪种算法。(也就是算法不再对天气作判断了,而是我(客户端)去做)
这在一定程度上增加了客户端的使用难度。但相应的却提高了系统的灵活性。

我们也能发现,如果多个地方都用到了某个策略,那么会在内存中产生多个相同的对象,如果数量庞大点,那对内存是一个很大的负担。我们应该用一个办法将这些重复的策略类给共享起来使用,降低内存的负担。

下篇文章将讲解如何用《享元模式》优化这一缺点。

  • 何时使用策略模式

我认为当在一个需要频繁增加 else if 或者 case 的情况时,就可以考虑使用策略模式减少它们之间的耦合度,提高扩展性。

本作品采用《CC 协议》,转载必须注明作者和本文链接
本帖由系统于 5年前 自动加精
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 5

那对天气的判断,在策略模式里没体现。这块逻辑怎么实现?

5年前 评论

@gangpula 这个就是策略模式的一个缺点了,策略模式只对算法进行封装,至于什么时候用,用哪个算法需要由客户端去选择。所以说会增加客户端的使用难度。

5年前 评论
未进化的类人猿 3年前
leung0826

对天气的判断也可以把 if else 去掉了,哪个天气 new 哪个类就行

5年前 评论

单纯为了改善if else可能有点"过度设计" , 为了使用设计模式而使用 , 策略模式更多情况下可能是为了复用算法

5年前 评论

@carlclone 是的,为了使用而使用是没有意义的。

5年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!