PHP开发 加入小组

298个成员 3006个话题 创建时间:2011-05-30

策略模式 PHP版

发表于 2012-12-20 7594 次查看

1.概述

        在软件开发中也常常遇到类似的情况,实现某一个功能有多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能。如查找、排序等,一种常用的方法是硬编码(Hard Coding)在一个类中,如需要提供多种查找算法,可以将这些算法写到一个类中,在该类中提供多个方法,每一个方法对应一个具体的查找算法;当然也可以将这些查找算法封装在一个统一的方法中,通过if…else…或者case等条件判断语句来进行选择。这两种实现方法我们都可以称之为硬编码,如果需要增加一种新的查找算法,需要修改封装算法类的源代码;更换查找算法,也需要修改客户端调用代码。在这个算法类中封装了大量查找算法,该类代码将较复杂,维护较为困难。如果我们将这些策略包含在客户端,这种做法更不可取,将导致客户端程序庞大而且难以维护,如果存在大量可供选择的算法时问题将变得更加严重。

例子1:一个菜单功能能够根据用户的“皮肤”首选项来决定是否采用水平的还是垂直的排列形式。同事可以灵活增加菜单那的显示样式。

例子2:出行旅游:我们可以有几个策略可以考虑:可以骑自行车,汽车,做火车,飞机。每个策略都可以得到相同的结果,但是它们使用了不同的资源。选择策略的依据是费用,时间,使用工具还有每种方式的方便程度 。

 

2.问题

 

如何让算法和对象分开来,使得算法可以独立于使用它的客户而变化?

 

3.解决方案

策略模式:定义一系列的算法,把每一个算法封装起来, 并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化。也称为政策模式(Policy)。(Definea family of algorithms,encapsulate each one, andmake them interchangeable. Strategy lets the algorithmvary independently from clients that use it. )

策略模式把对象本身和运算规则区分开来,其功能非常强大,因为这个设计模式本身的核心思想就是面向对象编程的多形性的思想。

4.适用性

 

当存在以下情况时使用Strategy模式
1)• 许多相关的类仅仅是行为有异。 “策略”提供了一种用多个行为中的一个行为来配置一个类的方法。即一个系统需要动态地在几种算法中选择一种。
2)• 需要使用一个算法的不同变体。例如,你可能会定义一些反映不同的空间 /时间权衡的算法。当这些变体实现为一个算法的类层次时 ,可以使用策略模式。
3)• 算法使用客户不应该知道的数据。可使用策略模式以避免暴露复杂的、与算法相关的数据结构。
4)• 一个类定义了多种行为 , 并且这些行为在这个类的操作中以多个条件语句的形式出现。将相关的条件分支移入它们各自的Strategy类中以代替这些条件语句。

 

5.结构

 

 

6.模式的组成

环境类(Context):用一个ConcreteStrategy对象来配置。维护一个对Strategy对象的引用。可定义一个接口来让Strategy访问它的数据。
抽象策略类(Strategy):定义所有支持的算法的公共接口。 Context使用这个接口来调用某ConcreteStrategy定义的算法。
具体策略类(ConcreteStrategy):以Strategy接口实现某具体算法。

7.效果

Strategy模式有下面的一些优点:
1) 相关算法系列 Strategy类层次为Context定义了一系列的可供重用的算法或行为。 继承有助于析取出这些算法中的公共功能。
2) 提供了可以替换继承关系的办法: 继承提供了另一种支持多种算法或行为的方法。你可以直接生成一个Context类的子类,从而给它以不同的行为。但这会将行为硬行编制到 Context中,而将算法的实现与Context的实现混合起来,从而使Context难以理解、难以维护和难以扩展,而且还不能动态地改变算法。最后你得到一堆相关的类 , 它们之间的唯一差别是它们所使用的算法或行为。 将算法封装在独立的Strategy类中使得你可以独立于其Context改变它,使它易于切换、易于理解、易于扩展。
3) 消除了一些if else条件语句 :Strategy模式提供了用条件语句选择所需的行为以外的另一种选择。当不同的行为堆砌在一个类中时 ,很难避免使用条件语句来选择合适的行为。将行为封装在一个个独立的Strategy类中消除了这些条件语句。含有许多条件语句的代码通常意味着需要使用Strategy模式。
4) 实现的选择 Strategy模式可以提供相同行为的不同实现。客户可以根据不同时间 /空间权衡取舍要求从不同策略中进行选择。

Strategy模式缺点:

1)客户端必须知道所有的策略类,并自行决定使用哪一个策略类:  本模式有一个潜在的缺点,就是一个客户要选择一个合适的Strategy就必须知道这些Strategy到底有何不同。此时可能不得不向客户暴露具体的实现问题。因此仅当这些不同行为变体与客户相关的行为时 , 才需要使用Strategy模式。
2 ) Strategy和Context之间的通信开销 :无论各个ConcreteStrategy实现的算法是简单还是复杂, 它们都共享Strategy定义的接口。因此很可能某些 ConcreteStrategy不会都用到所有通过这个接口传递给它们的信息;简单的 ConcreteStrategy可能不使用其中的任何信息!这就意味着有时Context会创建和初始化一些永远不会用到的参数。如果存在这样问题 , 那么将需要在Strategy和Context之间更进行紧密的耦合。
3 )策略模式将造成产生很多策略类:可以通过使用享元模式在一定程度上减少对象的数量。 增加了对象的数目 Strategy增加了一个应用中的对象的数目。有时你可以将 Strategy实现为可供各Context共享的无状态的对象来减少这一开销。任何其余的状态都由 Context维护。Context在每一次对Strategy对象的请求中都将这个状态传递过去。共享的 Strategy不应在各次调用之间维护状态。

8.实现

1)出行旅游:

uml:

代码实现:

  1. <?php  
  2. /** 
  3. * 策略模式 
  4. * 定义一系列的算法,把每一个算法封装起来, 并且使它们可相互替换。本模式使得算法可独立于使用它的客户而变化 
  5. */   
  6.   
  7.   
  8. /** 
  9. * 出行旅游 
  10. *  
  11. */  
  12. interface TravelStrategy{  
  13.     public function travelAlgorithm();  
  14. }   
  15.   
  16.   
  17. /** 
  18.  * 具体策略类(ConcreteStrategy)1:乘坐飞机 
  19.  */  
  20. class AirPlanelStrategy implements TravelStrategy {  
  21.     public function travelAlgorithm(){  
  22.         echo "travel by AirPlain", "<BR>\r\n";   
  23.     }  
  24. }   
  25.   
  26.   
  27. /** 
  28.  * 具体策略类(ConcreteStrategy)2:乘坐火车 
  29.  */  
  30. class TrainStrategy implements TravelStrategy {  
  31.     public function travelAlgorithm(){  
  32.         echo "travel by Train", "<BR>\r\n";   
  33.     }  
  34. }   
  35.   
  36. /** 
  37.  * 具体策略类(ConcreteStrategy)3:骑自行车 
  38.  */  
  39. class BicycleStrategy implements TravelStrategy {  
  40.     public function travelAlgorithm(){  
  41.         echo "travel by Bicycle", "<BR>\r\n";   
  42.     }  
  43. }   
  44.   
  45.   
  46.   
  47. /** 
  48.  *  
  49.  * 环境类(Context):用一个ConcreteStrategy对象来配置。维护一个对Strategy对象的引用。可定义一个接口来让Strategy访问它的数据。 
  50.  * 算法解决类,以提供客户选择使用何种解决方案: 
  51.  */  
  52. class PersonContext{  
  53.     private $_strategy = null;  
  54.   
  55.     public function __construct(TravelStrategy $travel){  
  56.         $this->_strategy = $travel;  
  57.     }  
  58.     /** 
  59.     * 旅行 
  60.     */  
  61.     public function setTravelStrategy(TravelStrategy $travel){  
  62.         $this->_strategy = $travel;  
  63.     }  
  64.     /** 
  65.     * 旅行 
  66.     */  
  67.     public function travel(){  
  68.         return $this->_strategy ->travelAlgorithm();  
  69.     }  
  70. }   
  71.   
  72. // 乘坐火车旅行  
  73. $person = new PersonContext(new TrainStrategy());  
  74. $person->travel();  
  75.   
  76. // 改骑自行车  
  77. $person->setTravelStrategy(new BicycleStrategy());  
  78. $person->travel();  
  79.   
  80. ?>   

 

2)排序策略:某系统提供了一个用于对数组数据进行操作的类,该类封装了对数组的常见操作,

如查找数组元素、对数组元素进行排序等。现以排序操作为例,使用策略模式设计该数组操作类,

使得客户端可以动态地更换排序算法,可以根据需要选择冒泡排序或选择排序或插入排序,

也能够灵活地增加新的排序算法。

9.与其他相关模式

1)状态模式

策略模式和其它许多设计模式比较起来是非常类似的。策略模式和状态模式最大的区别就是策略模式只是的条件选择只执行一次,而状态模式是随着实例参数(对象实例的状态)的改变不停地更改执行模式。换句话说,策略模式只是在对象初始化的时候更改执行模式,而状态模式是根据对象实例的周期时间而动态地改变对象实例的执行模式。

•可以通过环境类状态的个数来决定是使用策略模式还是状态模式。
•策略模式的环境类自己选择一个具体策略类,具体策略类无须关心环境类;而状态模式的环境类由于外在因素需要放进一个具体状态中,
以便通过其方法实现状态的切换,因此环境类和状态类之间存在一种双向的关联关系。
•使用策略模式时,客户端需要知道所选的具体策略是哪一个,而使用状态模式时,客户端无须关心具体状态,环境类的状态会根据用户的操作自动转换。
•如果系统中某个类的对象存在多种状态,不同状态下行为有差异,而且这些状态之间可以发生转换时使用状态模式;
如果系统中某个类的某一行为存在多种实现方式,而且这些实现方式可以互换时使用策略模式。

2)简单工厂的区别:点击打开链接

工厂模式是创建型模式 ,它关注对象创建,提供创建对象的接口. 让对象的创建与具体的使用客户无关。
策略模式是对象行为型模式 ,它关注行为和算法的封装 。它定义一系列的算法,把每一个算法封装起来, 并且使它们可相互替换。使得算法可独立于使用它的客户而变化

用我们上面提到旅行的例子:
我们去旅行。策略模式的做法:有几种方案供你选择旅行,选择火车好呢还是骑自行车,完全有客户自行决定去构建旅行方案(比如你自己需要去买火车票,或者机票)。而工厂模式是你决定哪种旅行方案后,不用关注这旅行方案怎么给你创建,也就是说你告诉我方案的名称就可以了,然后由工厂代替你去构建具体方案(工厂代替你去买火车票)。

上面的例子里面client代码:
$person = new PersonContext(new TrainStrategy());
$person->travel();
我们看到客户需要自己去创建具体旅行(new TrainStrategy())实例。传递的是具体实例。
而工厂模式你只要告诉哪种旅行就可以了,不是传递一个具体实例,而是一个标识(旅行方案标识)。

 

10.总结与分析

1)策略模式是一个比较容易理解和使用的设计模式,策略模式是对算法的封装,它把算法的责任和算法本身分割开,委派给不同的对象管理。策略模式通常把一个系列的算法封装到一系列的策略类里面,作为一个抽象策略类的子类。用一句话来说,就是“准备一组算法,并将每一个算法封装起来,使得它们可以互换”。
2)在策略模式中,应当由客户端自己决定在什么情况下使用什么具体策略角色。2)
3)策略模式仅仅封装算法,提供新算法插入到已有系统中,以及老算法从系统中“退休”的方便,策略模式并不决定在何时使用何种算法,算法的选择由客户端来决定。这在一定程度上提高了系统的灵活性,但是客户端需要理解所有具体策略类之间的区别,以便选择合适的算法,这也是策略模式的缺点之一,在一定程度上增加了客户端的使用难度。
 

转载指明原文出处:hguisu   设计模式 ( 十八 ) 策略模式Strategy(对象行为型) http://blog.csdn.net/hguisu/article/details/7558249

 

有几个评论也不错,也把他年过来:

 

代码没有体现消除掉if...else...语句的特性呢?
我怎么觉得始终还是需要if...else...

Re: 真实的归宿 2012-09-03 10:14发表 [回复]
回复niino:你在客户端肯定知道是要用什么策略算法的,所以没有if else之说。
比如SNS游戏的战斗,有pvp和pve,底层封装了战斗算法,而pvp和pve策略不同,在你决定要调用哪种策略时,已经知道调用用pvp还是pve。而不用判断if else判断
Re: qiaonnxy 2012-05-25 00:15发表 [回复]
回复niino:本来就没有消除if else
比如,某个客户端可能要根据情况选择一种出行方式,
还是要if else

换句话说, 这么多类,完全可以通过函数指针什么的代替
面向对象里面很多时候都声称自己可以消除if else
其实, 就是把if else 集中到某个地方而已, 并且常常称之为工厂模式。 工厂模式,其实就是if else
Re: 真实的归宿 2012-05-17 16:46发表 [回复] [引用] [举报]
回复niino:那些地方体现if else?
Re: goon1023 2012-05-24 16:01发表 [回复] [引用] [举报]
回复hguisu:比如用户需要根据实际情况选择想要乘火车或者自行车呢?
Re: 真实的归宿 2012-05-24 16:14发表 [回复] [引用] [举报]
回复goon1023:这个是客户端代码,客户端有权决定用哪种。客户端也不用if else来决定的用哪一种。
// 觉得乘坐火车就是乘坐火车,再没有if else
$person = new PersonContext(new TrainStrategy()); 
$person->travel();
Re: qiaonnxy 2012-05-25 00:18发表 [回复] [引用] [举报]
回复hguisu:客户端周一做火车, 周二自行车肿么办?
Re: 真实的归宿 2012-05-25 09:35发表 [回复] [引用] [举报]
回复qiaonnxy:首先,要明白所谓客户端,就是调用类库的。用现实的例子解释:旅社给你提供很多房间住宿,你星期一住哪个房间,星期二住哪个房间,完全由你自己觉得,旅社没法帮你决定。
即决定权在客户那里。
回到这个策略模式,不论周一坐火车还是周二骑自行车都是客户你自己决定的。类库没法法做到帮你决定哪一天要坐哪中交通工具,类库只是提供给你这么旅行策略,具体你怎么选择去安全客户端代码具体实现。
Re: qiaonnxy 2012-05-25 13:11发表 [回复] [引用] [举报]
回复hguisu:我不太明白, 一个系统难道不是应该各个地方都要考虑的吗?
你等于把这个问题,仍给了另外一个点而已.

那么,现在请您来帮忙解决这个客户端应肿么办?
Re: 真实的归宿 2012-05-25 13:39发表 [回复] [引用] [举报]
回复qiaonnxy:客户端怎么会肿呢? 问题也没有转移到另外一点。客户端需要自己去配置。可以使用xml来配置。
Re: niino 2012-06-04 16:34发表 [回复] [引用] [举报]
回复hguisu:那不还是把条件的判断丢给另外一个点了么...只是在这里不需要判断了,总是需要有地方做这个决策判断的啊

求楼主解释一下bridge模式和strategy模式之间的关系和区别, 谢谢
Re: 真实的归宿 2012-05-25 09:43发表 [回复] [引用] [举报]
回复sharemyfree:桥连和策略,这两个模式都没有相似点。
桥连应对多维度变化。策略模式封装算法。
发表回复
功能维护升级中,维护完成完后将再次开放,非常抱歉给您学习造成的不便。