策略模式 (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的事务管理是策略模式的另一个应用。Spring提供了一个统一的事务管理接口
PlatformTransactionManager
,而具体的事务管理策略(如JDBC、JPA、Hibernate)则由具体的实现类来提供。
- Spring的事务管理是策略模式的另一个应用。Spring提供了一个统一的事务管理接口
- Spring Security中的认证和授权策略:
- 在Spring Security中,可以通过实现
AuthenticationProvider
接口来提供不同的认证策略。此外,Spring Security支持不同类型的授权策略,允许开发者根据需求选择或自定义授权机制。
- 在Spring Security中,可以通过实现
- Spring MVC中的视图解析策略:
- 在Spring MVC框架中,视图解析器(
ViewResolver
)用于将视图名称解析为实际的视图对象。不同的视图解析策略支持不同类型的视图技术,如JSP、Thymeleaf、Freemarker等。
- 在Spring MVC框架中,视图解析器(
- AOP代理创建策略:
- 在Spring的AOP支持中,
AopProxyFactory
接口定义了创建AOP代理的策略。具体的代理策略(如基于JDK的动态代理或CGLIB代理)可以根据实际情况进行选择。
- 在Spring的AOP支持中,