策略模式初探

什么是策略模式?

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

定义了一族算法(业务规则);
封装了每个算法;
这族的算法可互换代替(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 协议》,转载必须注明作者和本文链接
本帖由系统于 6年前 自动加精
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 7

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

6年前 评论
eeelm 2周前

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

6年前 评论
未进化的类人猿 4年前
leung0826

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

6年前 评论

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

6年前 评论

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

6年前 评论

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