设计模式之策略模式(Strategy Pattern)
一.什么是策略模式(Strategy Pattern)?
从字面上理解,策略模式就是应用了某种“策略”的设计模式,而这个“策略”就是:把变化的部分封装起来。
其实这个理解有误,也是本文被反对一次的原因,例子没错,但对此模式的理解有偏差,修改内容已经追加在本文尾部,点我跳转>>
二.举个例子
假定现在我们需要用类来描述Dog
首先,所有的Dog都有外形(比如Color),有行为(比如Run)
于是我们很自然地定义了这样一个基类Dog:
public abstract class Dog { public abstract void display();//显示Dog的外形 public abstract void run();//定义Dog的Run行为 }
其它的Dog具体类全部继承自Dog基类就好了,很快,需求来了
有一只Red Dog,它是宠物犬,跑得很慢,叫声也很小
于是我们又定义了RedDog类:
public class RedDog { @override public void display(){ System.out.println("this is a red dog."); } @override public void run(){ System.out.println("it's running slowly."); } }
接着,来了一大批需求,Black Dog、Gray Dog、Pink Dog、BlackGrayDog、PinkGrayDog。。。一共100只Dog,除Color不同外,Run的速度也不同
于是我们定义了100个类来表示100种不同类型的Dog,每个类中都实现了Run方法与Display方法。
嗯~,看看我们的类图,没错,这个BigBang就是我们的类图了,好像很合理啊,有100种Dog当然会有100个类啊,而且具体Dog类当然要继承自Dog基类吧?嗯,好像是这样的,而且这样好像也没什么不好,至少现在看不出来有什么不妥。。。
-------
又一个新的需求来了,我们发现Dog不仅可以Run,还可以Bark
自然而然地,我们想到了给Dog基类定义一个新行为Bark,然后在100个具体类中逐一重写了Bark方法。。。
虽然过程有些麻烦,不过好在我们还是解决了问题,现在所有的Dog都可以Bark了
-------
这天,来了一只新Dog,它叫ToyDog,是玩具狗,不会Run也不能Bark,只是个玩偶
于是我们让ToyDog继承了Dog基类,重写了Run方法和Bark方法(方法体为空,因为Toy不会Run也不会Bark)
问题好像也被完美解决了,至少现在看来不存在什么问题
-------
很多天后,ToyDog工厂技术革新了,新产品被装上了电池,可以Bark了
没关系,我们修改了ToyDog类,实现了Bark
-------
我们初始化了一只ToyDog,它欢快的BarkBarkBark,过了一会儿,电池没电了,ToyDog不能Bark了,但是我们的ToyDog类显示它还可以Bark。。。
*******
现在,终于出问题了吧,我们发现Dog问题中最难处理的部分就是Run、Bark这两个行为,一旦发生变化我们就需要修改具体Dog类,甚至是Dog基类,不仅需要花费大量的时间,而且所有具体Dog类中都实现了Run与Bark方法,显得很臃肿。还有最重要的问题,我们无法动态地修改Dog的行为,比如小Dog跑得慢叫声小,长大后跑得快叫声大,也无法应对上面提到的电池没电导致的行为变化问题。。。。这一系列的问题想向我们说明一点:这从一开始就是一个糟糕的设计。
三.如何应用策略模式?
策略模式要求把变化的部分封装起来,首先,我们要找到代码中频频发生变化的部分
在上一个例子中,变化的部分是什么?
1.Run行为
2.Bark行为
3.其它可能存在的行为
...
下面我们把这些行为封装起来(以Run为例):
package StrategyPattern; /** * @author ayqy * 定义Run接口 * */ public interface IRun { public void run();//定义run行为 }
package StrategyPattern; /** * @author ayqy * 实现RunQuickly行为 * */ public class RunQuickly implements IRun{ @Override public void run() { System.out.println("it's running quicky."); } }
package StrategyPattern; /** * @author ayqy * 实现RunSlowly行为 * */ public class RunSlowly implements IRun{ @Override public void run() { System.out.println("it's running slowly."); } }
package StrategyPattern; /** * @author ayqy * 实现RunNoWay行为 * */ public class RunNoWay implements IRun{ @Override public void run() { System.out.println("it isn't able to run."); } }
Bark行为的封装与之类似
封装好“变化”之后,我们的Dog基类也要做相应改变:
package StrategyPattern; /** * @author ayqy * 定义Dog基类 * */ public abstract class Dog { IBark bark; IRun run; public abstract void display();//显示Dog的外形 public IBark getBark() { return bark; } public void setBark(IBark bark) { this.bark = bark; } public IRun getRun() { return run; } public void setRun(IRun run) { this.run = run; } }
注意,我们只封装了容易发生变化的部分(Bark与Run),而没有封装Display方法(Dog的外形应该比较fixed吧),什么才是“变化的部分”?这需要我们仔细考虑,视具体情景而定
现在来看看我们新的具体Dog类吧
package StrategyPattern; /** * @author ayqy * 实现RedDog * */ public class RedDog extends Dog{ public RedDog() { //红狗是宠物狗 //跑得慢,叫声小 super.setRun(new RunSlowly()); super.setBark(new BarkQuietly()); } @Override public void display() { System.out.println("this is a red dog."); } }
类被明显瘦身了吧,而且我们现在还能在运行时动态地修改Dog的行为,看到策略模式的威力了吧。
四.效果示例
编写一个测试类:
package StrategyPattern; /** * @author ayqy * 测试策略模式<br/> * 策略模式,简单的说就是要求把“变化”封装起来,以隔离“变化”对其它部分的影响 * */ public class Test { /** * @param args */ public static void main(String[] args) { System.out.println("------- 创造一只Red Dog -------"); Dog redDog = new RedDog(); showDogInfo(redDog); System.out.println("\n------- 创造一只Black Dog -------"); Dog blackDog = new BlackDog(); showDogInfo(blackDog); System.out.println("\n------- 创造一只Toy Dog -------"); Dog toyDog = new ToyDog(); showDogInfo(toyDog); System.out.println("\n------- 技术革新,现在Toy Dog可以小声叫了 -------"); toyDog.setBark(new BarkQuietly()); showDogInfo(toyDog); //上面的代码并不是最优的,只是为了说明策略模式 //一个很明显的问题是Dog redDog = new RedDog(); //我们在面向具体对象编码,而不是设计模式所提倡的面向接口编码 //可以定义一个Dog工厂来生产Dog对象以求模块之间更松的耦合 } /** * @param dog * 显示Dog相关信息 */ private static void showDogInfo(Dog dog) { dog.display(); dog.run.run(); dog.bark.bark(); } }
运行结果示例:
五.总结
策略模式的核心是要把频繁发生变化的部分封装起来,作用是把变化部分的影响隔离开,避免局部的变化对其它fixed部分造成影响,设计时可能需要更多的时间,但便于维护、复用与扩展,在本例中,Run、Bark行为都可以在新的类(如Pig)中复用;一旦行为发生变化我们只需要修改各个行为接口,最多再对Dog基类做简单修改就可以从容地应对变化了。
上面的内容有些偏差,但勉强可以看,只是不太准确,下面作以准确的描述:
策略模式的思想确实是封装,但这里的”策略“并不是指”把变化封装起来“,这是在反复读了几遍原文后发现的(《Head First 设计模式》第一章感觉有点不太好,第一遍理解偏了)
这里的”策略“近似于算法
策略模式不太容易理解(虽然笔者极力避免照搬原文,但万一再理解偏了就误导别人了。。),书上的准确定义是这样的:
策略模式定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
个人反复读过几遍之后,觉得原文想表达的东西是这样的:
- 1.策略模式核心是对算法的封装(还有一种设计模式叫”模版方法模式“,也是对算法的封装,区别与联系放在里面介绍,点我跳转>>)
- 2.专注于实现算法(策略)的选择,支持运行时动态改变策略
- 3.具体实现是把变化的部分找出来,定义为接口,每个接口对应一组算法,每一个都是一种策略
- 4.同一接口下的算法是可以相互替换的
- 5.算法是独立于客户代码的,也就是对算法封装的具体体现