策略模式初探

三海 2020年10月15日 163次浏览

什么是策略模式?

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

定义了一族算法(业务规则);
封装了每个算法;
这族的算法可互换代替(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 的情况时,就可以考虑使用策略模式减少它们之间的耦合度,提高扩展性。