我有个大胆的想法!就是给大妈来解释这个模式。为什么跟妹子一起的时候说相对论,量子力学,妹子们扭头就想走?为什么跟妹子们说杨振宁的物理建树有多高,妹子们只关心他82的时候娶了个28的姑娘?问题出在哪里?废话,你讲的一点都不生动!嗯,就是这样。从现在开始,学会做个会讲故事的程序猿!
好了,先上本文目录。
- 什么是“策略模式”
- 定义
- 结构
- 怎么用“策略模式”
- 为什么用“策略模式”
- 适用场景
- 总结
什么是“策略模式”
定义
这么跟你解释吧。就是一首同样的广场舞的歌,不同的广场的大妈,可能有不同的舞蹈动作(我就能用健美操的动作,来跳“苍茫的天涯是我的爱”);三国时候著名的“曹冲称象”,对比宰了称或者直接称就妙多了;在比如,隔壁的马小容想去找老王的助理宋二吉去酒店打麻将,怎么去呢?戴口罩骑自行车,走路,叫滴滴blabla...都可以(为让进来的观众记忆深刻,我特地ps了一张图)。
去酒店的可选方式好了,作为一条咸鱼或者一根韭菜,你知道这些就够了。大妈:小伙子怎么说话呢,我不服,我就要做有梦想的咸鱼!好好好,别慌,我们来“寻梦,撑一支长蒿,向青草更青处漫溯...”。我们得把上面的2个例子抽象成具有一般性的描述,来适用和这类似的案例。我们可能经历这样的一个抽象过程:为达目的 (跳舞,去酒店打麻将...),不择手段 (这里请理解成多种手段)!或者是条条道路通罗马。咳咳,虽然有那么点意思了,但是总觉得没上升到一定高度有没有?那我们换个说话,如下:
策略模式 定义了一系列的算法,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户。
这就是策略模式的定义。什么,妹子你要走???请留步!!!我再给你在讲讲日食的时候是怎么证明广义相对论的。。。好吧,不开玩笑了,这个模式的定义,仔细看看,是不是和“条条道路通罗马”,“为达目的,不择手段”有点差不多的味道???呵呵,我知道这么不生动的东西,跟大妈直接说没多大用处。所以,顶多记住下面的策略模式的三个特点完事。看看wiki的定义。其实表达的意思差不多。
In computer programming, the strategy pattern (also known as the policy pattern) is a behavioral software design pattern that enables selecting an algorithm at runtime. The strategy pattern
- defines a family of algorithms,
- encapsulates each algorithm, and
- makes the algorithms interchangeable within that family.
——摘自 wiki
什么,看不懂?别慌,这里有翻译。逼格一下子上去了有没有?
策略模式作为一种软件设计模式,指对象有某个行为,但是在不同的场景中,该行为有不同的实现算法。策略模式:
- 定义了一族算法(业务规则);
- 封装了每个算法;
- 这族的算法可互换代替(interchangeable)。
好了,言归正传,我知道知识越抽象,越难以让大妈理解并接受,更关键的是,容易忘记!!!所以我还是换个说法:为达目的,不择手段(多种手段)。
好了,到这里为止,大妈篇就过去了,知道这个概念差不多够在程序猿面前装逼了。下面程序猿篇的代码部分与应用部分,就算我有勇气跟你说,你也没勇气听。所以,我在这里加上 三 条三八线。
结构
好了,现在说话就不拐弯抹角了!咳咳,哪有那么多生动的故事给你一个抠脚大汉讲?搞技术本来就是不可爱的事情。所以这里,我选择直接上图。
wiki strategy uml
在上面左边的类图里面,Context不直接实现算法(具体行为)。而是存储了一个Stategy的引用来间接实现,这就使得Context和算法的实现分离了(解耦)。在右边的时序图里面,可以看到运行时的交互。这里在把涉及到的三个元素在总结一下:
- 环境Context:持有一个Strategy的引用。
- 抽象策略Strategy:定义是何种行为动作。
- 具体的策略:不同算法实现Strategy中定义的行为动作。
代码实现很简单,见下(From wiki中文)。这里的依赖注入采用的构造器。一般策略模式也会用setStrategy(Strategy)的方式来注入。
//StrategyExample test application
class StrategyExample {
public static void main(String[] args) {
Context context;
// Three contexts following different strategies
context = new Context(new FirstStrategy());
context.execute();
context = new Context(new SecondStrategy());
context.execute();
context = new Context(new ThirdStrategy());
context.execute();
}
}
// The classes that implement a concrete strategy should implement this
// The context class uses this to call the concrete strategy
interface Strategy {
void execute();
}
// Implements the algorithm using the strategy interface
class FirstStrategy implements Strategy {
public void execute() {
System.out.println("Called FirstStrategy.execute()");
}
}
class SecondStrategy implements Strategy {
public void execute() {
System.out.println("Called SecondStrategy.execute()");
}
}
class ThirdStrategy implements Strategy {
public void execute() {
System.out.println("Called ThirdStrategy.execute()");
}
}
// Configured with a ConcreteStrategy object and maintains a reference to a Strategy object
class Context {
Strategy strategy;
// Constructor
public Context(Strategy strategy) {
this.strategy = strategy;
}
public void execute() {
this.strategy.execute();
}
}
怎么用“策略模式”
为什么用“策略模式”
“策略模式”用起来有什么好处?其实我的体会就是,这个模式很好的体现了OCP(开闭原则)。最直观的体现是在可拓展性上面。当然,在最后的总结部分也会总结一下好处。
直接结合我在定义里面列举的例子来看,都是广场舞《最炫民族风》,可以用健美操二级来跳,也有一些广场的大妈可能拿着扇子来跳,也可以自定义舞蹈动作来跳...针对这种情况,一种常规的方式是将多种算法写在一个类中。如下:
class SquareDance{
void aerobics(){...}//健美操
void handFan(){...}//折扇
void custom(){...}//自定义
}
当然,也可以将这些算法封装在一个统一的方法中,通过if...else...或者case等条件判断语句来选择具体的算法。和上面这个常规的方式一样,都属于硬编码。多个算法集中在一个类的时候,类就变得臃肿,维护成本变高,最明显的就是,违反了OCP开闭原则,没有做到对修改关闭。例如这时候,北京某广场的大妈发明了一种新跳法,怎么维护进去呢?只能侵入性的去修改已经封装了的源代码了。在下面的小节里面,我拿汽车的例子来分析,更能体会到策略设计的好处。
适用场景
直白点说,就是一个行为有多种可选实现算法的时候就可以考虑使用了。因为我不喜欢把一些规则戒律化。当然你也可以具体分析特定的场景,如果满足定义中的引用的wiki策略模式定义中的三个特点,就肯定可以用策略模式了。不过我在一本书中也看到这样描述的,这里顺手也贴一下(反正我看了是没冲动去记住,顶多理解一下,表达自己对作者的尊敬)。一定要提的是,不要为了模式而模式。人是活的。
- 针对同一类型问题的多种处理方式,仅仅是具体行为有差别时
- 需要安全地封装多种同一类型的操作时。
- 出现同一抽象类有多个子类,而又需要使用if-else或者case来选择具体子类时。
平时生活中,你善于总结,善于发现的话,这样的场景就有很多。就像蓝猫淘气主题曲里面的“只要你爱想爱问爱动脑,天地间奇妙的问题你全明了”(呸!明明是头发白的更快了)。言归正传,购物的时候,针对不同客户的不同优惠;Android动画源码中的时间插值器;开源项目UniversalImageLoader的缓存策略;现在汽车的双引擎设计以及刹车系统的不同实现等;小到生活中,钓鱼用哪个型号的钩子,配蚯蚓鱼饵还是面粉鱼饵?团建活动时,公司给了好几个地方,你选择去哪里...
根据策略模式,一个类的行为,不应该被继承,而是应该使用接口封装起来。我用汽车来说明。简化场景,这里我们考虑汽车的两个行为:刹车和加速。既然这两个行为,在不同的车型里面差别很大,一个常规的解决方式,就是在子类里面实现这些行为。而这种方式有很明显的缺陷,我列出来:
- 在每个子类型号中,都必须实现这两个行为。随着车型的增加,维护这两个行为的工作量非常可观。
- 很可能在某些型号的车型里面存在重复的实现。
- 不进子类来看代码的话,想要知道每个型号的具体行为是不容易的。
等等,这里是不是发现,和 《Head First设计模式》 里面第一章的讲的鸭子设计一模一样?汽车的刹车和加速,鸭子的飞和叫。。。傻子也能看出来就是一个模子刻的啊。
而策略模式是使用组合的方式,而不是继承。在策略模式里面,不同的行为定义成不同的接口,然后指定类来实现这些接口。这就使得行为和使用这个行为的对象,更好的解耦。行为的具体实现如果改变,对使用到这个行为的类而言,不用做任何改变。而且,只要注入进不同的实现(策略)就能以很小的代价让类改变行为。行为在运行时和设计时都能改变,非常灵活。比如说,一个汽车Car对象的刹车行为可以是BrakeWithABS()和Brake()中的任何一个:
/* Encapsulated family of Algorithms
* Interface and its implementations
*/
public interface IBrakeBehavior {
public void brake();
}
public class BrakeWithABS implements IBrakeBehavior {
public void brake() {
System.out.println("Brake with ABS applied");
}
}
public class Brake implements IBrakeBehavior {
public void brake() {
System.out.println("Simple Brake applied");
}
}
/* Client that can use the algorithms above interchangeably */
public abstract class Car {
protected IBrakeBehavior brakeBehavior;
public void applyBrake() {
brakeBehavior.brake();
}
public void setBrakeBehavior(final IBrakeBehavior brakeType) {
this.brakeBehavior = brakeType;
}
}
/* Client 1 uses one algorithm (Brake) in the constructor */
public class Sedan extends Car {
public Sedan() {
this.brakeBehavior = new Brake();
}
}
/* Client 2 uses another algorithm (BrakeWithABS) in the constructor */
public class SUV extends Car {
public SUV() {
this.brakeBehavior = new BrakeWithABS();
}
}
/* Using the Car example */
public class CarExample {
public static void main(final String[] arguments) {
Car sedanCar = new Sedan();
sedanCar.applyBrake(); // This will invoke class "Brake"
Car suvCar = new SUV();
suvCar.applyBrake(); // This will invoke class "BrakeWithABS"
// set brake behavior dynamically
suvCar.setBrakeBehavior( new Brake() );
suvCar.applyBrake(); // This will invoke class "Brake"
}
}
总结
策略模式主要用来分离算法,在相同的行为抽象下有不同的具体实现策略。这个模式很好的演示了开闭原则,也就是定义抽象,注入不同的实现,从而达到很好的可扩展性。
优点
- 结构清晰明了、使用简单直观;
- 耦合度相对而言较低,扩展方便;
- 操作封装也更为彻底,数据更为安全。
缺点
- 随着策略的增加,子类也会变得繁多;
- 客户端必须知道所有的策略类,并自行决定具体使用哪一个策略类。
网友评论