策略模式 (Strategy Pattern)

定义

策略模式(Strategy Pattern)是一种行为型设计模式,它定义了算法家族,分别封装起来,让它们之间可以互相替换。策略模式使得算法的变化独立于使用算法的客户。这种模式涉及到三个角色:

  • 上下文(Context):用来维护对某个策略对象的引用。
  • 策略接口(Strategy):定义了一个公共接口,各种不同的算法以不同的方式实现这个接口。
  • 具体策略(Concrete Strategy):实现策略接口的具体算法。
解决的问题
  • 算法的选择与实现分离
    • 在许多情况下,特定任务可以有多种算法或策略。策略模式允许将算法的选择与其实现分离开来,从而使算法可以独立于使用它们的客户端代码变化。
  • 动态替换算法
    • 在运行时根据不同情况或上下文动态选择最适合的算法。策略模式提供了一种机制,使得可以在运行时更改对象的行为或算法。
  • 消除条件语句
    • 在传统的编程实践中,多种算法或行为常常通过条件语句(如 if-else 或 switch-case)来实现。策略模式通过将每种算法封装在独立的类中,帮助减少条件语句的使用,使代码更易于维护和理解。
  • 扩展性
    • 新增算法或改变现有算法时,策略模式使得这些变更变得更容易,因为它避免了对现有代码的修改。只需添加新的策略类即可扩展新的行为。
  • 封装算法族
    • 当一个应用程序需要一组算法,并且希望在不同的情况下应用不同的算法时,策略模式允许将这些算法封装在一个个独立的策略类中,并在运行时选择使用哪一个
使用场景
  • 多种算法或行为可供选择
    • 当有多种相关的类仅在行为上有所差异时,策略模式允许根据具体情况选择适当的行为。
  • 运行时选择算法
    • 如果在运行时需要动态地选择算法,策略模式提供了一种灵活的方式来实现这一点,使得可以根据上下文条件或输入选择最合适的算法。
  • 避免写大量的条件判断语句
    • 在不使用策略模式的情况下,实现多种算法或行为常常需要使用复杂的条件判断语句(如if-else或switch-case)。策略模式通过封装算法来简化这些判断逻辑。
  • 算法的封装和隔离
    • 当需要封装涉及复杂逻辑或数据的算法,并且希望将算法的实现细节隔离开来时,策略模式是一个理想的选择。
  • 需要易于扩展和修改的算法族
    • 如果预计在未来需要新增或更改算法,策略模式使得扩展成为可能,且不会影响到使用算法的客户端代码。
示例代码
// 策略接口
interface Strategy {
    public int doOperation(int num1, int num2);
}

// 具体策略
class OperationAdd implements Strategy{
    public int doOperation(int num1, int num2) {
        return num1 + num2;
    }
}

class OperationSubtract implements Strategy{
    public int doOperation(int num1, int num2) {
        return num1 - num2;
    }
}

class OperationMultiply implements Strategy{
    public int doOperation(int num1, int num2) {
        return num1 * num2;
    }
}

// 上下文
class Context {
    private Strategy strategy;

    public Context(Strategy strategy){
        this.strategy = strategy;
    }

    public int executeStrategy(int num1, int num2){
        return strategy.doOperation(num1, num2);
    }
}

// 客户端
public class StrategyPatternDemo {
    public static void main(String[] args) {
        Context context = new Context(new OperationAdd());		
        System.out.println("10 + 5 = " + context.executeStrategy(10, 5));

        context = new Context(new OperationSubtract());		
        System.out.println("10 - 5 = " + context.executeStrategy(10, 5));

        context = new Context(new OperationMultiply());		
        System.out.println("10 * 5 = " + context.executeStrategy(10, 5));
    }
}
主要符合的设计原则
  • 开闭原则(Open-Closed Principle)
    • 策略模式允许在不修改现有代码的情况下引入新的策略。这是因为你可以添加新的策略类而不需要更改使用策略的代码。换句话说,系统应该对扩展开放(可以添加新的策略),对修改关闭(不需要改变现有策略的使用方式)。
  • 单一职责原则(Single Responsibility Principle)
    • 在策略模式中,每个策略类都只有一个改变的原因——该策略的实现。这符合单一职责原则,即一个类应该只有一个改变的原因。
  • 依赖倒置原则(Dependency Inversion Principle)
    • 策略模式通常要求策略的消费者依赖于策略接口,而不是具体的策略实现,这符合依赖倒置原则。这意味着高层模块(如使用策略的类)不应该依赖于低层模块(如具体策略的实现),而应该依赖于抽象。
  • 里氏替换原则(Liskov Substitution Principle)
    • 此原则指出,子类的对象应该能够替换其父类对象被使用而不引入错误。在策略模式中,不同的策略实现可以互换使用,只要它们遵循同一个策略接口。
在JDK中的应用
  • java.util.Comparator
    • Comparator 接口是策略模式的一个经典例子。它定义了一个策略接口,用于比较两个对象。在排序操作中,比如 Collections.sort()Arrays.sort(),可以传入不同的 Comparator 实现,以提供不同的排序策略。
  • java.io.FileFilter 和 java.io.FilenameFilter
    • 这两个接口允许用户根据文件的属性(如名称、大小、类型等)决定是否接受文件。这些接口的实现可以被用作策略,传递给文件列表方法,例如 File.listFiles(FileFilter)
  • java.text.BreakIterator
    • java.text 包中,BreakIterator 定义了不同的文本分割策略,如按单词、句子或字符分割。不同的 BreakIterator 实现代表了不同的分割策略。
  • java.util.concurrent.ThreadPoolExecutor 中的 RejectedExecutionHandler
    • RejectedExecutionHandler 接口定义了当任务被拒绝执行时的策略。例如,可以选择在拒绝时抛出异常、直接丢弃任务、或将任务放在队列中等待。
  • java.util.logging.Level
    • 在日志记录中,可以通过设置不同的 Level 对象来控制日志信息的输出。这些 Level 对象实际上定义了不同的日志记录策略。
在Spring中的应用
  • 资源加载策略(ResourceLoader)
    • Spring使用资源加载策略来加载不同类型的资源。通过 ResourceLoader 接口,Spring可以加载来自不同来源(如文件系统、类路径、URL等)的资源,而具体的加载逻辑则取决于 ResourceLoader 的实现。
  • 事务管理策略
    • Spring的事务管理是策略模式的另一个应用。Spring提供了一个统一的事务管理接口 PlatformTransactionManager,而具体的事务管理策略(如JDBC、JPA、Hibernate)则由具体的实现类来提供。
  • Spring Security中的认证和授权策略
    • 在Spring Security中,可以通过实现 AuthenticationProvider 接口来提供不同的认证策略。此外,Spring Security支持不同类型的授权策略,允许开发者根据需求选择或自定义授权机制。
  • Spring MVC中的视图解析策略
    • 在Spring MVC框架中,视图解析器(ViewResolver)用于将视图名称解析为实际的视图对象。不同的视图解析策略支持不同类型的视图技术,如JSP、Thymeleaf、Freemarker等。
  • AOP代理创建策略
    • 在Spring的AOP支持中,AopProxyFactory 接口定义了创建AOP代理的策略。具体的代理策略(如基于JDK的动态代理或CGLIB代理)可以根据实际情况进行选择。