策略模式属于对象的行为模式。
其用意是针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换。
策略模式使得算法可以在不影响到客户端的情况下发生变化。
策略模式相当于可插入式的算法。
当准备在一个系统里使用策略模式时,首先必须找到需要包装的算法,看看算法是否可以从环境中分割开来,
最后在考察这些算法是否会在以后发生变化。
策略模式的结构
这个模式涉及三个角色:
- 环境角色(Context): 持有一个Strategy类的引用。
- 抽象策略角色(Strategy): 这是一个抽象角色,通常由一个接口或抽象类实现。
- 具体策略角色(ConcreteStrategy): 包装了相关的算法或行为。
源代码:
环境类
public class Context { private Strategy strategy; //策略方法 public void contextInterface(){ strategy.strategyInterface(); } }
抽象策略类
public abstract class Strategy { //策略方法 public abstract void strategyInterface(); }
具体策略类
public class ConcreteStrategy extends Strategy { //策略模式 @Override public void strategyInterface() { //write your algorithm code here } }
这里给出的仅仅是策略模式的最小实现,因此具体策略角色才只有一个。
一般而言,有意义的策略模式的应用都会涉及到多于一个的具体策略角色。
模式的实现
策略模式实现要注意的地方:
1. 经常见到的是,所有具体的策略类都有一些公关的方法。这时候,就应当把这些公关的行为放到共同的抽象角色Strategy类里面。当然这时候抽象策略角色必须要用java抽象类实现,而不能是java接口。
2. 策略模式在每一个时刻都只能使用一个策略对象,但是有的时候一个应用程序同时和几个策略对象相联系。
也就是说,在应用程序启动时,所有的策略对象就已经被创立出来,而应用程序可以在几个策略对象之间调换。
Java语言内部的例子
AWT中的LayoutManager
java.awt类库需要在运行期间动态的由客户端决定一个Container对象怎样排列它所有的GUI构件。
Java语言提供了几种不同的排序方式,包装在不同的类里:
- BorderLayout
- FlowLayout
- GridLayout
- GridBagLayout
- CardLayout
LayoutManager的类图:
排序策略系统
假设要设计一个排序系统,动态的决定采用二元排序(Binary Sort)、泡沫排序(Bubble Sort)、堆栈排序(Heap Sort)、
快速排序(Quick Sort)、基数排序(Radix Sort)。
显然,采用策略模式把几种排序算法包装到不同的算法类里面,让所有的算法类具有相同的接口,就是一个很好的设计。
排序策略系统的设计如下:
客户端必须决定在何时使用哪一个排序算法,也就是所,这个决定不是在模式内部决定的。
另外,策略模式不适合于处理同事嵌套多于一个算法的情形。
一般而言,策略模式只适用于客户端在几种算法中选择一种的情形,并不适用于客户端同时需要几种算法的情形。
例如,算法四,在所有的折扣算法计算后,总的折扣数不能超过1000.
这就意味着客户端必须首先适用折扣算法一、二、三计算出折扣总值后,再使用算法四。
这种重复使用多种算法的情形不是单纯的策略模式可以处理的,需要进一步使用装饰模式。
在什么情况下使用策略模式
- 如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态的让一个对象在许多行为中选择一种行为。
- 一个系统需要动态的在几种算法中选择一种。
- 一个系统的算法使用的数据不可以让客户端知道。策略模式可以避免让客户端涉及到不必要接触到的复杂的知识和只与算法有关的数据。
- 如果一个对象有很多行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。此时,使用策略模式,把这些行为转移到相应的具体策略类里面,就可以避免使用难以维护的多重条件选择语句,并体现面向对象设计的概念。
策略模式的优点和缺点
优点:
- 策略模式提供了管理相关算法族的办法。策略类的等级结构定义了一个算法或行为族。恰当使用继承可以把公共的代码移动到父类里面,从而避免重复的代码。
- 策略模式提供了可以替换继承关系的办法。继承可以处理多种算法或行为。如果不用策略模式,那么使用算法或行为的环境类就可能会有一些资料,每一个子类提供一个不同的算法或行为。但是,这样一来,算法或行为的使用者就和算法或行为的本身混在了一起。决定使用哪种算法和采取哪种行为的逻辑就和算法或行为的逻辑混合在一起,从而不可能再独立的演化。继承使得动态改变算法或行为变得不可能。
- 使用策略模式可以避免使用多重条件转移语句。多重转移语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重转移语句里面,比使用继承的办法还要原始和落后。
缺点:
- 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。也就是说,策略模式只适用于客户端知道所有算法或行为的情况。
- 策略模式造成很多的策略类。有时候可以通过把依赖于环境的状态保存到客户端里面,而将策略类设计成可共享的,这样策略类实例可以被不同的客户端使用。也就是说,可以使用享元模式来减少对象的数量。