模板模式主要起到代码复用和扩展的作用。除此之外,我们还讲到了回调,它跟模板模式的作用类似,但使用起来更加灵活。它们之间的主要区别在于代码实现,模板模式基于继承来实现,回调基于组合来实现。

今天,我们开始学习另外一种行为型模式,策略模式。在实际的项目开发中,这个模式也比较常用。最常见的应用场景是,利用它来避免冗长的 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(简单)

策略类的定义比较简单,包含一个策略接口和一组实现这个接口的策略类。因为所有的策略类都实现相同的接口,所以,客户端代码基于接口而非实现编程,可以灵活地替换不同的策略。
image.png

  1. public class Main {
  2. public static void main(String[] args) {
  3. // 由于实例化不同的策略,所以最终在调用 context.ContextInterface(); 时,所获得的结果就不尽相同
  4. Context context;
  5. context = new Context(new ConcreteStrategyA());
  6. context.ContextInterface();
  7. context = new Context(new ConcreteStrategyB());
  8. context.ContextInterface();
  9. context = new Context(new ConcreteStrategyC());
  10. context.ContextInterface();
  11. }
  12. }
  13. /**
  14. * Strategy类,定义所有支持的算法的公共接口
  15. */
  16. abstract class Strategy {
  17. public abstract void AlgorithmInterface();
  18. }
  19. /**
  20. * ConcreteStrategy,封装了具体的算法或行为,继承于Strategy
  21. */
  22. class ConcreteStrategyA extends Strategy {
  23. //算法A实现方法
  24. @Override
  25. public void AlgorithmInterface() {
  26. System.out.println("算法A实现");
  27. }
  28. }
  29. //具体算法B
  30. class ConcreteStrategyB extends Strategy {
  31. //算法B实现方法
  32. @Override
  33. public void AlgorithmInterface() {
  34. System.out.println("算法B实现");
  35. }
  36. }
  37. //具体算法C
  38. class ConcreteStrategyC extends Strategy {
  39. //算法C实现方法
  40. @Override
  41. public void AlgorithmInterface() {
  42. System.out.println("算法C实现");
  43. }
  44. }
  45. /**
  46. * Context,用一个 ConcreteStrategy 来配置,维护一个对 Strategy 对象的引用。
  47. */
  48. class Context {
  49. private Strategy strategy;
  50. // 初始化时传入具体的策略对象
  51. public Context(Strategy strategy) {
  52. this.strategy = strategy;
  53. }
  54. // 根据具体的策略对象,调用其算法的方法
  55. public void ContextInterface() {
  56. strategy.AlgorithmInterface();
  57. }
  58. }

image.png

3.2、GoF 的改进(简单)

因为策略模式会包含一组策略,在使用它们的时候,一般会通过类型(type)来判断创建哪个策略来使用。为了封装创建逻辑,我们需要对客户端代码屏蔽创建细节。我们可以把根据 type 创建策略的逻辑抽离出来,放到工厂类中。

  1. package Strategy.version2;
  2. import java.util.HashMap;
  3. import java.util.Map;
  4. public class Main {
  5. public static void main(String[] args) {
  6. // 由于实例化不同的策略,所以最终在调用 context.ContextInterface(); 时,所获得的结果就不尽相同
  7. Context context;
  8. context = new Context(StrategyFactory.getStrategy("A"));
  9. context.ContextInterface();
  10. context = new Context(StrategyFactory.getStrategy("B"));
  11. context.ContextInterface();
  12. context = new Context(StrategyFactory.getStrategy("C"));
  13. context.ContextInterface();
  14. }
  15. }
  16. /**
  17. * Strategy类,定义所有支持的算法的公共接口
  18. */
  19. abstract class Strategy {
  20. public abstract void AlgorithmInterface();
  21. }
  22. /**
  23. * ConcreteStrategy,封装了具体的算法或行为,继承于Strategy
  24. */
  25. class ConcreteStrategyA extends Strategy {
  26. //算法A实现方法
  27. @Override
  28. public void AlgorithmInterface() {
  29. System.out.println("算法A实现");
  30. }
  31. }
  32. //具体算法B
  33. class ConcreteStrategyB extends Strategy {
  34. //算法B实现方法
  35. @Override
  36. public void AlgorithmInterface() {
  37. System.out.println("算法B实现");
  38. }
  39. }
  40. //具体算法C
  41. class ConcreteStrategyC extends Strategy {
  42. //算法C实现方法
  43. @Override
  44. public void AlgorithmInterface() {
  45. System.out.println("算法C实现");
  46. }
  47. }
  48. /**
  49. * Context,用一个 ConcreteStrategy 来配置,维护一个对 Strategy 对象的引用。
  50. */
  51. class Context {
  52. private Strategy strategy;
  53. // 初始化时传入具体的策略对象
  54. public Context(Strategy strategy) {
  55. this.strategy = strategy;
  56. }
  57. // 根据具体的策略对象,调用其算法的方法
  58. public void ContextInterface() {
  59. strategy.AlgorithmInterface();
  60. }
  61. }
  62. /**
  63. * 策略工厂类
  64. */
  65. class StrategyFactory {
  66. private static final Map<String, Strategy> strategies = new HashMap<>();
  67. static {
  68. strategies.put("A", new ConcreteStrategyA());
  69. strategies.put("B", new ConcreteStrategyB());
  70. strategies.put("C", new ConcreteStrategyC());
  71. }
  72. public static Strategy getStrategy(String type) {
  73. if (type == null || type.isEmpty()) {
  74. throw new IllegalArgumentException("type should not be empty.");
  75. }
  76. return strategies.get(type);
  77. }
  78. }

image.png

一般来讲,如果策略类是无状态的,不包含成员变量,只是纯粹的算法实现,这样的策略对象是可以被共享使用的,不需要在每次调用 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;
    }
}

image.png

3.3、结合反射(中等)

对于 Java 语言来说,我们可以通过反射来避免对策略工厂类的修改。具体是这么做的:我们通过一个配置文件或者自定义的 annotation 来标注都有哪些策略类;策略工厂类读取配置文件或者搜索被 annotation 标注的策略类,然后通过反射动态地加载这些策略类、创建策略对象;当我们新添加一个策略的时候,只需要将这个新添加的策略类添加到配置文件或者用 annotation 标注即可。

4、总结

4.1、优缺点

1)优点

我们知道,策略模式包含一组可选策略,客户端代码一般如何确定使用哪个策略呢?最常见的是运行时动态确定使用哪种策略,这也是策略模式最典型的应用场景。这里的“运行时动态”指的是,我们事先并不知道会使用哪个策略,而是在程序运行期间,根据配置、用户输入、计算结果等这些不确定因素,动态决定使用哪种策略。因此,借助策略模式,可以将客户端中的大量 if - else 转移到策略工厂中,避免在客户端进行大量的分支判断。

另外一个策略模式的优点是简化了单元测试,因为每 个算法都有自己的类,可以通过自己的接口单独测试。每个算法可保证它没有错误,修改其中任一个时也不会影响其他 的算法。

2)缺点

流程比较复杂的话,策略类会增多。

4.2、关于策略模式的一些问题

如果一个系统的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题。