策略模式 (Strategy Pattern): 策略模式定义了一系列算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户端。把一个类中经常改变或者将来可能改变的部分提取出来,作为一个接口,然后在类中包含这个对象的实例,这样类的实例在运行时就可以随意调用实现了这个接口的类的行为。策略模式是一种行为型模式。
-
举个例子,鸭子有很多属性:飞行、鸣叫、展现形式等。鸭子又有很多类型,比如北京鸭,会游泳会叫不会飞;野鸭,会游泳会叫会飞;玩具鸭,不会游泳不会叫不会飞。
-
由于要做一个通用的鸭子对象模型,所以我们需要把把这个鸭子创建成一个抽象的对象,将所有鸭子的通用属性放在抽象类中,而将差异化的属性在继承类中实现。
-
考虑到代码的可扩展性和后期的维护,又将继承的这种模式进行改变,将两个类结合起来使用,即组合的方式。这种做法和继承不同的地方在于,鸭子的行为不是继承来的,而是和适当行为对象“组合”来的。
-
使用组合建立系统具有很大的弹性,不仅可将算法族封装成类,更可以“在运行时动态地改变行为”,只要组合的行为对象符合正确的接口标准即可。
原理UML图
组成
-
环境类(Context):用一个ConcreteStrategy对象来配置。维护一个对Strategy对象的引用。可定义一个接口来让Strategy访问它的数据。
-
抽象策略类(Strategy):定义所有支持的算法的公共接口。 Context使用这个接口来调用某ConcreteStrategy定义的算法。
-
具体策略类(ConcreteStrategy):以Strategy接口实现某具体算法。
优点
-
策略模式提供了对开闭原则的完美支持,用户可以在不修改原有系统的基础上选择算法或行为,也可以灵活地增加新的算法或行为。
-
策略模式提供了管理相关的算法族的办法。策略类的等级结构定义了一个算法或行为族。恰当使用继承可以把公共的代码转移到父类里面,从而避免重复的代码。
-
策略模式提供了可以替换继承关系的办法。继承可以处理多种算法或行为。如果不是用策略模式,那么使用算法或行为的环境类就可能会有一些子类,每一个子类提供一个不同的算法或行为。但是,这样一来算法或行为的使用者就和算法或行为本身混在一起,不符合单一职责原则。决定使用哪一种算法或采取哪一种行为的逻辑就和算法或行为的逻辑混合在一起,从而不可能再独立演化。继承使得动态改变算法或行为变得不可能。
-
使用策略模式可以避免使用多重条件转移语句。多重转移语句不易维护,它把采取哪一种算法或采取哪一种行为的逻辑与算法或行为的逻辑混合在一起,统统列在一个多重转移语句里面,比使用继承的办法还要原始和落后。
缺点
- 客户端必须知道所有的策略类,并自行决定使用哪一个策略类。这就意味着客户端必须理解这些算法的区别,以便适时选择恰当的算法类。换言之,策略模式只适用于客户端知道所有的算法或行为的情况。
- 策略模式造成很多的策略类,每个具体策略类都会产生一个新类。有时候可以通过把依赖于环境的状态保存到客户端里面,而将策略类设计成可共享的,这样策略类实例可以被不同客户端使用。换言之,可以使用享元模式来减少对象的数量。
适用场景
- 多个类只区别在表现行为不同,可以使用Strategy模式,在运行时动态选择具体要执行的行为。
- 需要在不同情况下使用不同的策略(算法),或者策略还可能在未来用其它方式来实现。
- 对客户隐藏具体策略(算法)的实现细节,彼此完全独立。
实例
某鸭园有三种鸭子,分别是北京鸭、野鸭和玩具鸭,他们都会游泳,但是飞行行为不一样。
- 北京鸭飞翔技能一般
- 野鸭飞翔技能高超
- 玩具鸭不会飞
该鸭园在将来可能还要根据需要引入其他类型鸭子。试使用策略模式设计该鸭园的养鸭方案。
实例类图
- FlyBehavior类,充当抽象策略类
package strategy;
public interface FlyBehavior {
void fly(); //子类具体实现
}
- GoodFlyBehavior类,充当具体策略类
package strategy;
public class GoodFlyBehavior implements FlyBehavior {
@Override
public void fly() {
System.out.println("飞翔技术高超!");
}
}
- NoFlyBehavior 类,充当具体策略类
package strategy;
public class NoFlyBehavior implements FlyBehavior {
@Override
public void fly() {
System.out.println("不会飞翔!");
}
}
- BadFlyBehavior类,充当具体策略类
package strategy;
public class BadFlyBehavior implements FlyBehavior {
@Override
public void fly() {
System.out.println("飞翔技术一般!");
}
}
- Duck 类,充当抽象环境类
package strategy;
public abstract class Duck {
//属性,策略接口
FlyBehavior flyBehavior;
public Duck() {
}
public abstract void display(); //显示鸭子信息
public void swim() {
System.out.println("鸭子会游泳!");
}
public void fly() {
if (flyBehavior != null) {
flyBehavior.fly();
}
}
public void setFlyBehavior(FlyBehavior flyBehavior) {
this.flyBehavior = flyBehavior;
}
}
- PekingDuck 类,充当具体环境类
package strategy;
public class PekingDuck extends Duck {
//北京鸭飞翔技术一般
public PekingDuck() {
flyBehavior = new BadFlyBehavior();
}
@Override
public void display() {
System.out.println("~~北京鸭~~");
}
}
- ToyDuck类,充当具体环境类
package strategy;
public class ToyDuck extends Duck {
//玩具鸭不会飞
public ToyDuck() {
flyBehavior = new NoFlyBehavior();
}
@Override
public void display() {
System.out.println("~~玩具鸭~~");
}
}
- WildDuck类,充当具体环境类
package strategy;
public class WildDuck extends Duck {
//野鸭很会飞
public WildDuck() {
flyBehavior = new GoodFlyBehavior();
}
@Override
public void display() {
System.out.println("这是野鸭~");
}
}
- Client 类,客户端测试类
package strategy;
public class Client {
public static void main(String[] args) {
WildDuck wildDuck = new WildDuck();
wildDuck.display();
wildDuck.fly();
wildDuck.swim();
System.out.println("--------------");
ToyDuck toyDuck = new ToyDuck();
toyDuck.display();
toyDuck.fly();
toyDuck.swim();
System.out.println("--------------");
PekingDuck pekingDuck = new PekingDuck();
pekingDuck.display();
pekingDuck.fly();
pekingDuck.swim();
System.out.println("--------------");
//动态改变某个对象的行为,北京鸭不能飞
pekingDuck.setFlyBehavior(new NoFlyBehavior());
System.out.println("北京鸭的实际飞行能力:");
pekingDuck.fly();
}
}
运行结果:
如果需要增加新的飞行方式,原有代码均无需修改,只要增加一个新的飞行方式类作为抽象策略类的子类即可,完全符合开闭原则。