一 通过实例认识策略模式
首先让我们来说明一个问题,在软件业惟一不变的是什么呢?是变化。这是不是有点冷笑话的感觉?不管怎样,这就是事实,要想在软件行业混,我们就必须得接受这一点,我想绝大多数的程序员也是接受了这一点的,要不为什么有程序员是体力劳动者这一方法呢(当然变化只是造成程序员繁重工作的一个主要点,并不是全部原因)?
人的天性总是懒惰的,既然变化是不可避免的,那我们就要想办法来减轻变化带来的副作用,这就引出了软件行业一个重要的设计原则---“封装变化”,即找出应用中可能需要变化的地方,把它们独立出来,不要和那些不变化的代码混在一起。
下面让我们看一个需求变化的例子,假设我们现在有一个模拟鸭子的应用,其UML类图如下:
现在变化来了,我们需要给鸭子加入飞行的功能。面对这个需求变化,你想到的第一个解决方法是什么,不会是在Duck基类加入一个Fly方法,让所有的鸭子都可行飞行吧?如果是的话,那么恭喜你,你将会拥有一个会飞的橡皮鸭子(这是不是很酷?但却不现实),有人可能要说了,我可以在橡皮鸭子子类中override Fly方法,让它什么也不做,这样橡皮鸭子就不能飞行了,这是可以,但是如果我们又有了一个新的鸭子类型----诱饵鸭子,这可是一个即不会飞行,也不会叫的鸭子,我们该拿它怎么办呢?
这时我们的脑子里又有了第二个解决方法,将Quack和Fly从基类移出,做成接口,如果鸭子能飞或是会叫,就让它实现对应的接口。恭喜恭喜,你又有了一个更不好的解决方法,因为这样一来,会有很多重复的代码,如果飞行或是叫的方法发生变化,我们需要在每一个实现了接口的类中进行修改,这将是一个多么麻烦的工作啊。
这也不行,那也不行,我们究竟应该怎么办呢?别急,让我们请出主角―――Strategy Pattern。它就是专为这个时刻而生的。
策略模式的用意是针对一组算法,将每一个算法封装到具有共同接口的独立类中,从而使得它们可以相互替换。策略模式让算法的变化独立于使用算法的客户,并且根据需要,客户可以在运行时动态改变行为。
说了Strategy Pattern的简介后,针对上面的例子,我们应该怎么来应用它呢?很简单,首先我们需要先定义两个算法族,一个是飞行的,一个是叫的。对于Fly,我们定义了一个IFlyable接口,飞行算法族中的每一个类都实现这个接口。对于Quack,我们定义了IQuackable接口,叫算法族中的每一个算法类都实现了这个接口。然后我们在Duck基类中定义了两个属性,FlyBehavior是IFlyable的实例,QuackBeharior是IQuack的实例。这样Duck对象就可以动态的根据需要选择一个自己的算法。UML图和示例代码如下:
二 什么时候应用使用Strategy Pattern:
在什么时候应该使用Strategy Pattern呢?毕竟我们学了就是要用的,这里让我们来看一个吕震宇所列的几个情形:
1. 如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
2. 一个系统需要动态地在几种算法中选择一种。那么这些算法可以包装到一个个的具体算法类里面,而这些具体算法类都是一个抽象算法类的子类。换言之,这些具体算法类均有统一的接口,由于多态性原则,客户端可以选择使用任何一个具体算法类,并只持有一个数据类型是抽象算法类的对象。
3. 一个系统的算法使用的数据不可以让客户端知道。策略模式可以避免让客户端涉及到不必要接触到的复杂的和只与算法有关的数据。
4. 如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。此时,使用策略模式,把这些行为转移到相应的具体策略类里面,就可以避免使用难以维护的多重条件选择语句,并体现面向对象设计的概念。
三 Strategy Pattern的优点和缺点:
同样让我们来看吕震宇所列:
优点有:
1. 策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族。恰当使用继承可以把公共的代码移到父类里面,从而避免重复的代码。
2. 策略模式提供了可以替换继承关系的办法。继承可以处理多种算法或行为。如果不是用策略模式,那么使用算法或行为的环境类就可能会有一些子类,每一个子类提供一个不同的算法或行为。但是,这样一来算法或行为的使用者就和算法或行为本身混在一起。决定使用哪一种算法或采取哪一种行为的逻辑就和算法或行为的逻辑混合在一起,从而不可能再独立演化。继承使得动态改变算法或行为变得不可能。
3. 使用策略模式可以避免使用多重条件转移语句。多重转移语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重转移语句里面,比使用继承的办法还要原始和落后。
缺点有:
1. 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。换言之,策略模式只适用于客户端知道所有的算法或行为的情况。
2. 策略模式造成很多的策略类。有时候可以通过把依赖于环境的状态保存到客户端里面,而将策略类设计成可共享的,这样策略类实例可以被不同客户端使用。换言之,可以使用享元模式来减少对象的数量。
四 Strategy Pattern和其它模式的比较
1 Strategy Pattern和Template Method的比较:
Strategy Pattern定义了一个算法族,封装了算法的变化,客户可以通过委托模型轻易的使用不同的算法,这降低了依赖,也提高了灵活性,但正像前面提到的,Strategy Pattern会产生很多的策略类,并且要求客户对这些算法类比较熟悉,毕竟应该使用那个类不是Strategy Pattern说了算的,而是由客户根据实际情况决定的。
Template Method(我们会在以后介绍)利用了继承的方法,在基类中定义了算法的框架,并将算法的可变部分声明为可重写的,由具体的子类使用,这样就可以利用同样的框架,完成不同的功能。它的优点是效率高一点,并且比较简单,(这也是我们平常最常用的一个设计模式,虽然我们可能并没有注意),但同时,Template Pattern的依赖性太高,它没有办法让客户在运行时改变行为。
2 Strategy pattern 和State Pattern的比较
Strategy pattern将算法族定义在不同的子类中,由用户根据需要选择,并且Strategy pattern是继承的一种弹性替代方案,通过组合,我们可以提供运行时改变对象行为的方法。
State Pattern是将行为封装在状态对象中,类的行为根据状态的不同而变化,这点对用户是透明的。另外State Pattern是多条件判断的替代方案,我们通过多态来去掉多条件判断这一不好的编程习惯。
五 我们学到了什么
通过上面的介绍,我们不仅认识了Strategy Pettern,也学到了几个OO设计的原则,这们分别是:
软件开发中惟一不变的是变化,所以要封装变化。――这点在上面已有介绍。
针对接口编程,而不要针对实现编程。――“解释一下,这里所说的接口并不是狭义的interface,而是广义的.即变量的声明类型应该是超类型,通常是一个抽象类或是一个超类,如此,只要是具体实现此超类型的类所产生的对象,都可以指定给这个变量。这也意味着,声明类时,不用理会,以后执行时的真正对象类型。
多用组合,少用继承。――继承虽说是OO的三大特性之一,但也有许多不好的地方,必须合理的运用它,否则会有很多麻烦,这点我们在以后会有更详细的介绍。
六 参考资料
<Head First 设计模式>
出处:http://zhangronghua.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。