曾经有个一个面试,问的是设计模式相关的问题。也就是鸭子问题,很多有经验的人因该很熟悉,就是策略模式里面的经典例子。虽然我知道些策略模式的知识,但是我却不晓得这个经典的例子,也怪我没有去看经典的书籍。我当时还一直纳闷,为什么要举例鸭子相关的,现在想想,是那个时候确实知识不够,献丑了。这策略模式网络上高手已经写了很多,我写它更多的目的在于自己,留下点记忆,也许几年后我会回过头还在看自己写的文章。
这个问题的提问我先陈述一下:先说有五十只鸭子怎么描述,保存。我说呢,先抽象出一个Dark类,把鸭子应该有的数据和行为抽象出来。然后它又说每次鸭子都会游泳(这个我还好理解),那么怎么来写这个类。我就说Dark里写一个抽象函数就可以了。接着他说,既然这样,有的鸭子会飞怎么处理呢(鸭子会飞?我当时懵了,或许野鸭吧)?由于不是很理解这个逻辑,随便就说继承也可以的,不会飞的鸭子方法为空不久可以。然后他说不可以,这样每个鸭子都要实现飞的行为,不符合逻辑;我接着又说用接口,把具有飞行为的鸭子实现这个接口。然而我的回答还是不让他满意,那以后的这个飞的行为要改变呢?以后要是再添加其他的行为怎么半呢?他说可以考虑代码的重用和以后的扩展。。。。。,但是我感觉用普通的接口可以弄阿,我努力思考着他强掉的是什么呢。。。。
前端时间,我看了一篇也是关于策略模式的文章,里面就提到了鸭子问题,感觉文章也写的不错(链接我就放在最后写吧),他们好像似乎看的一本书吧(也有可能是同一个人哦)。不过我感觉他问的方式也是有些问题,从最后他给我说出采用策略模式来看,他所强调的好像并不是策略模式的特点。比如他说不是所有的鸭子都会飞,那么单就这个最直接的就是把飞抽象成一个借口。他说考虑到代码重用和扩展,我感觉这里途径太多了,能是我在这里说的清的吗,哎。
言归正传,那就好好说说这个策略模式吧。 现看看wiki里搜索的吧:In computer programming, the strategy pattern (also known as the policy pattern) is a particular software design pattern, whereby algorithms can be selected at runtime. Formally speaking, the strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.[1] 它强调策略模式是算法可以在运行的时候被选择的模式。一般来讲策略模式定义了一组封装好可以互换的算法,策略让客户端使用的算法独立出来。 例如,一个类执行这样的一个验证行为,这个验证行为就是根据输入的数据的类型(这里的数据就是用户的选择或者其他的不同的因素)使用策略来选择不同的验证算法。这些因素只有在运行的时候才知道,并且需要执行不同的验证算法。这个被验证对象封装的验证策略可能在系统不同的地方被用于其他的验证对象(甚至是不同的系统)而无须重写代码。
1 // The classes that implement a concrete strategy should implement this
2
3 // The context class uses this to call the concrete strategy
4 interface Strategy {
5 int execute(int a, int b);
6 }
7
8 // Implements the algorithm using the strategy interface
9 class ConcreteStrategyAdd implements Strategy {
10
11 public int execute(int a, int b) {
12 System.out.println("Called ConcreteStrategyAdd's execute()");
13 return a + b; // Do an addition with a and b
14 }
15 }
16
17 class ConcreteStrategySubtract implements Strategy {
18
19 public int execute(int a, int b) {
20 System.out.println("Called ConcreteStrategySubtract's execute()");
21 return a - b; // Do a subtraction with a and b
22 }
23 }
24
25 class ConcreteStrategyMultiply implements Strategy {
26
27 public int execute(int a, int b) {
28 System.out.println("Called ConcreteStrategyMultiply's execute()");
29 return a * b; // Do a multiplication with a and b
30 }
31 }
32
33 // Configured with a ConcreteStrategy object and maintains a reference to a Strategy object
34 class Context {
35
36 private Strategy strategy;
37
38 // Constructor
39 public Context(Strategy strategy) {
40 this.strategy = strategy;
41 }
42
43 public int executeStrategy(int a, int b) {
44 return strategy.execute(a, b);
45 }
46 }
47
48 //StrategyExample test application
49
50 class StrategyExample {
51
52 public static void main(String[] args) {
53
54 Context context;
55
56 // Three contexts following different strategies
57 context = new Context(new ConcreteStrategyAdd());
58 int resultA = context.executeStrategy(3,4);
59
60 context = new Context(new ConcreteStrategySubtract());
61 int resultB = context.executeStrategy(3,4);
62
63 context = new Context(new ConcreteStrategyMultiply());
64 int resultC = context.executeStrategy(3,4);
65 }
66 }
跟strategy pattern的UML图相似的另一个冤家就是Bridge pattern。但是,他们在机制上并不相同。Strategy pattern重在行为,而Bridge pattern重点在结构。环境角色与策略角色之间的耦合度高于抽象角色与实现化角色。而我们看到他们的区别很简单:Strategy是调用者不变,变的只是被调用的方法;Bridge是调用者和被调用者都可以改变。Bridge pattern的UML如图:
根据策略模式,类的行为不应该被继承,相反他们应该用接口封装。例如,考虑一个Car类,两个可能方法:刹车和加速。
由于模块间加速和刹车行为经常改变,常见的方法是在子类中实现这些行为。这种方式有着明显的缺点:每new一个car必须要声明刹车与加速行为。那么这些管理这些行为(这里指刹车与加速的行为)的功过随着模型(这里指的是car)的增多而变的巨大。此外,每个模块在不考察其代码的情况下并不容易觉得它的真实行为。
Strategy pattern使用组合来代替继承。在Stragegy pattern里,行为被定义在独立里的接口和具体实现这些接口的类。具体类封装了这些接口,这样可以降低这些行为与使用这些行为类的耦合性。行为可以在不破坏使用它类的情况下改变。
现在看来,Strategy pattern并不是什么高深的东西,只不过是面向接口编程一个经典应用而已。而Bridge pattern也只不过是把Strategy pattern里的环境角色抽象了一步而已。
记住一个原则:优先把组合代替你的继承。