模板模式主要起到代码复用和扩展的作用。除此之外,我们还讲到了回调,它跟模板模式的作用类似,但使用起来更加灵活。它们之间的主要区别在于代码实现,模板模式基于继承来实现,回调基于组合来实现。
今天,我们开始学习另外一种行为型模式,策略模式。在实际的项目开发中,这个模式也比较常用。最常见的应用场景是,利用它来避免冗长的 if-else 或 switch 分支判断。不过,它的作用还不止如此。它也可以像模板模式那样,提供框架的扩展点等等。
1、什么是策略模式?
策略模式,英文全称是 Strategy Design Pattern。在 GoF 的《设计模式》一书中,它是这样定义的:
Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.
翻译成中文就是:定义一族算法类,将每个算法分别封装起来,让它们可以互相替换。策略模式可以使算法的变化独立于使用它们的客户端(这里的客户端代指使用算法的代码)。
2、为什么要使用策略模式?
我们知道,工厂模式是解耦对象的创建和使用,观察者模式是解耦观察者和被观察者。策略模式跟两者类似,也能起到解耦的作用,不过,它解耦的是策略的定义、创建、使用这三部分。
3、例子
3.1、GoF(简单)
策略类的定义比较简单,包含一个策略接口和一组实现这个接口的策略类。因为所有的策略类都实现相同的接口,所以,客户端代码基于接口而非实现编程,可以灵活地替换不同的策略。
public class Main {public static void main(String[] args) {// 由于实例化不同的策略,所以最终在调用 context.ContextInterface(); 时,所获得的结果就不尽相同Context context;context = new Context(new ConcreteStrategyA());context.ContextInterface();context = new Context(new ConcreteStrategyB());context.ContextInterface();context = new Context(new ConcreteStrategyC());context.ContextInterface();}}/*** Strategy类,定义所有支持的算法的公共接口*/abstract class Strategy {public abstract void AlgorithmInterface();}/*** ConcreteStrategy,封装了具体的算法或行为,继承于Strategy*/class ConcreteStrategyA extends Strategy {//算法A实现方法@Overridepublic void AlgorithmInterface() {System.out.println("算法A实现");}}//具体算法Bclass ConcreteStrategyB extends Strategy {//算法B实现方法@Overridepublic void AlgorithmInterface() {System.out.println("算法B实现");}}//具体算法Cclass ConcreteStrategyC extends Strategy {//算法C实现方法@Overridepublic void AlgorithmInterface() {System.out.println("算法C实现");}}/*** Context,用一个 ConcreteStrategy 来配置,维护一个对 Strategy 对象的引用。*/class Context {private Strategy strategy;// 初始化时传入具体的策略对象public Context(Strategy strategy) {this.strategy = strategy;}// 根据具体的策略对象,调用其算法的方法public void ContextInterface() {strategy.AlgorithmInterface();}}

3.2、GoF 的改进(简单)
因为策略模式会包含一组策略,在使用它们的时候,一般会通过类型(type)来判断创建哪个策略来使用。为了封装创建逻辑,我们需要对客户端代码屏蔽创建细节。我们可以把根据 type 创建策略的逻辑抽离出来,放到工厂类中。
package Strategy.version2;import java.util.HashMap;import java.util.Map;public class Main {public static void main(String[] args) {// 由于实例化不同的策略,所以最终在调用 context.ContextInterface(); 时,所获得的结果就不尽相同Context context;context = new Context(StrategyFactory.getStrategy("A"));context.ContextInterface();context = new Context(StrategyFactory.getStrategy("B"));context.ContextInterface();context = new Context(StrategyFactory.getStrategy("C"));context.ContextInterface();}}/*** Strategy类,定义所有支持的算法的公共接口*/abstract class Strategy {public abstract void AlgorithmInterface();}/*** ConcreteStrategy,封装了具体的算法或行为,继承于Strategy*/class ConcreteStrategyA extends Strategy {//算法A实现方法@Overridepublic void AlgorithmInterface() {System.out.println("算法A实现");}}//具体算法Bclass ConcreteStrategyB extends Strategy {//算法B实现方法@Overridepublic void AlgorithmInterface() {System.out.println("算法B实现");}}//具体算法Cclass ConcreteStrategyC extends Strategy {//算法C实现方法@Overridepublic void AlgorithmInterface() {System.out.println("算法C实现");}}/*** Context,用一个 ConcreteStrategy 来配置,维护一个对 Strategy 对象的引用。*/class Context {private Strategy strategy;// 初始化时传入具体的策略对象public Context(Strategy strategy) {this.strategy = strategy;}// 根据具体的策略对象,调用其算法的方法public void ContextInterface() {strategy.AlgorithmInterface();}}/*** 策略工厂类*/class StrategyFactory {private static final Map<String, Strategy> strategies = new HashMap<>();static {strategies.put("A", new ConcreteStrategyA());strategies.put("B", new ConcreteStrategyB());strategies.put("C", new ConcreteStrategyC());}public static Strategy getStrategy(String type) {if (type == null || type.isEmpty()) {throw new IllegalArgumentException("type should not be empty.");}return strategies.get(type);}}

一般来讲,如果策略类是无状态的,不包含成员变量,只是纯粹的算法实现,这样的策略对象是可以被共享使用的,不需要在每次调用 getStrategy() 的时候,都创建一个新的策略对象。针对这种情况,我们可以使用上面这种工厂类的实现方式,事先创建好每个策略对象,缓存到工厂类中,用的时候直接返回。
相反,如果策略类是有状态的,根据业务场景的需要,我们希望每次从工厂方法中,获得的都是新创建的策略对象,而不是缓存好可共享的策略对象,那我们就需要按照如下方式来实现策略工厂类。
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
// 由于实例化不同的策略,所以最终在调用 context.ContextInterface(); 时,所获得的结果就不尽相同
Context context;
context = new Context(StrategyFactory.getStrategy("A"));
context.ContextInterface();
context = new Context(StrategyFactory.getStrategy("B"));
context.ContextInterface();
context = new Context(StrategyFactory.getStrategy("C"));
context.ContextInterface();
}
}
/**
* Strategy类,定义所有支持的算法的公共接口
*/
abstract class Strategy {
public abstract void AlgorithmInterface();
}
/**
* ConcreteStrategy,封装了具体的算法或行为,继承于Strategy
*/
class ConcreteStrategyA extends Strategy {
//算法A实现方法
@Override
public void AlgorithmInterface() {
System.out.println("算法A实现");
}
}
//具体算法B
class ConcreteStrategyB extends Strategy {
//算法B实现方法
@Override
public void AlgorithmInterface() {
System.out.println("算法B实现");
}
}
//具体算法C
class ConcreteStrategyC extends Strategy {
//算法C实现方法
@Override
public void AlgorithmInterface() {
System.out.println("算法C实现");
}
}
/**
* Context,用一个 ConcreteStrategy 来配置,维护一个对 Strategy 对象的引用。
*/
class Context {
private Strategy strategy;
// 初始化时传入具体的策略对象
public Context(Strategy strategy) {
this.strategy = strategy;
}
// 根据具体的策略对象,调用其算法的方法
public void ContextInterface() {
strategy.AlgorithmInterface();
}
}
/**
* 策略工厂类
*/
class StrategyFactory {
public static Strategy getStrategy(String type) {
if (type == null || type.isEmpty()) {
throw new IllegalArgumentException("type should not be empty.");
}
if (type.equals("A")) {
return new ConcreteStrategyA();
} else if (type.equals("B")) {
return new ConcreteStrategyB();
} else if (type.equals("C")) {
return new ConcreteStrategyC();
}
return null;
}
}

3.3、结合反射(中等)
对于 Java 语言来说,我们可以通过反射来避免对策略工厂类的修改。具体是这么做的:我们通过一个配置文件或者自定义的 annotation 来标注都有哪些策略类;策略工厂类读取配置文件或者搜索被 annotation 标注的策略类,然后通过反射动态地加载这些策略类、创建策略对象;当我们新添加一个策略的时候,只需要将这个新添加的策略类添加到配置文件或者用 annotation 标注即可。
4、总结
4.1、优缺点
1)优点
我们知道,策略模式包含一组可选策略,客户端代码一般如何确定使用哪个策略呢?最常见的是运行时动态确定使用哪种策略,这也是策略模式最典型的应用场景。这里的“运行时动态”指的是,我们事先并不知道会使用哪个策略,而是在程序运行期间,根据配置、用户输入、计算结果等这些不确定因素,动态决定使用哪种策略。因此,借助策略模式,可以将客户端中的大量 if - else 转移到策略工厂中,避免在客户端进行大量的分支判断。
另外一个策略模式的优点是简化了单元测试,因为每 个算法都有自己的类,可以通过自己的接口单独测试。每个算法可保证它没有错误,修改其中任一个时也不会影响其他 的算法。
2)缺点
流程比较复杂的话,策略类会增多。
4.2、关于策略模式的一些问题
如果一个系统的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题。
