一: 基本概念
策略模式: 策略模式是一种行为型设计模。核心思想是对算法进行封装,委派给不同对象进行管理。将每个算法封装到具有公共接口的具体策略类中,由客户自己选择要执行的算法,并且在不对客户影响的情况下对算法进行修改。
二:结构
角色 | 关系 | 作用 |
---|---|---|
抽象策略类 | 作为所有具体策略类的父类 | 定义一个公共的接口,并且定义算法的抽象方法 |
具体实现类 | 抽象策略类的实现类 | 实现抽象策略类的抽象方法,实现具体的算法逻辑。 |
上下文类 | 维护一个抽象策略类示例 | 接收具体策略类示例,并调用实现的抽象方法 |
UML类图
三:使用示例与Java中的源码使用
1、使用示例
1、简单示例
//策略模式接口类,定义了抽象策略方法
public interface Strategy {
void doSomeThing();
}
//A策略
public class AStrategy() implements Strategy{
@Override
public void doSomeThing() {
System.out.println("A 策略执行了");
}
}
//B策略
public class BStrategy() implements Strategy{
@Override
public void doSomeThing() {
System.out.println("B 策略执行了");
}
}
//上下文, 用于调用策略方法
public class StrategyContext{
//保存抽象策略的引用
private Strategy strategy;
//由用户自己传入策略
public StrategyContext(Strategy strategy) {
this.strategy = strategy;
}
public void executeStrategy(){
strategy.doSomeThing();
}
}
//客户端调用
public class StrategyPattern{
public static void main(String[] args) {
AStrategy aStrategy = new AStrategy();
StrategyContext context = new StrategyContext(aStrategy);
context.executeStrategy();
BStrategy bStrategy = new BStrategy();
StrategyContext strategyContext = new StrategyContext(bStrategy);
strategyContext.executeStrategy();;
}
}
执行结果
A 策略执行了
B 策略执行了
2、项目使用示例(策略模式跟工厂模式配合使用)
项目背景:
我们平台有一个绩效考勤模块,用于考核坐席的工作情况。该模块中有一个绩效模板功能用于选择考核的坐席与我们定义的十几个通用的绩效考核指标。在这种情况下,当前企业每个坐席可能有着不同的绩效考核指标,如果简单的使用 if…else 语句,会造成多重条件判断,导致代码过于臃肿跟效率不高,降低了代码的可维护性。并且后续还有可能针对某个企业做自定义考核规则,也需要去修改if else语句 ,这样也违反了开闭原则。绩效考核指标是不同的算法,而不同的模板可以选择不同的指标,也就是一个任务有不同的方式来实现。刚好符合策略模式的使用场景,在这节我们将策略模式跟工厂模式配合起来使用。
具体实现逻辑:
定义了一个绩效规则策略接口,接口定义了两个方法,分别为绩效规则方法跟获取id方法。绩效规则方法用于实现类实现具体的绩效规则,获取id方法则用于与工厂类配合来注册具体的绩效策略类实例。具体的绩效规则策略类则实现上面的策略接口,实现自己的绩效规则逻辑与提供唯一id,并使用@Component注解将自己交给spring进行管理。
在工厂类中使用了Map用来存储所有的绩效策略接口的实现类,key为每个实现类的id,value则为实现类对应的bean。工厂类实现了ApplicationContextAware接口, 用来获取实现了绩效策略接口的所有bean,并将所有bean都注册到map中。
在使用时,只需要使用工厂类并传入id,则可以获取对应的绩效策略实现类。如果要新增策略规则类,则只需要实现绩效规则策略接口,并将自己交给spring管理即可。这样即符合了开闭原则,也不会产生大量的if else语句块。
//抽象绩效策略类用于定义通用方法
public interface IPerformanceStrategy {
//每个策略使用不同的id, 用于配合工厂模式
String getId();
//绩效计算的抽象方法, 具体实现交由子类决定
Integer calculate(AgentLoginStatistics.Params params);
}
//离线指标计算策略实现类。该类交给spring进行构建(因为需要依赖其他的dao跟service)
@Component
public class OfflineTimeStrategy implements IPerformanceStrategy{
@Override
public String getId() {
return "ID1";
}
@Override
public Integer calculate(AgentLoginStatistics.Params params) {
return 2;
}
}
//在线指标计算策略实现类。该类交给spring进行构建(因为需要依赖其他的dao跟service)
@Component
public class OnlineTimeStrategy implements IPerformanceStrategy{
@Override
public String getId() {
return "ID2";
}
@Override
public Integer calculate(AgentLoginStatistics.Params params) {
return 1;
}
}
2、Javac中的源码使用
1、ThreadPoolExecutor的拒绝策略
ThreadPoolExecutor的拒绝策略就是策略模式的典型应用。ThreadPoolExecutor的构造方法可以由用户自己传入一个实现了RejectedExecutionHandler的自定义拒绝执行类,由我们自己提供在线程与队列都满了的情况下对新提交的执行任务的处理策略。
RejectedExecutionHandler有四个实现类,:AbortPolicy(默认)、CallerRunsPolicy、DiscardPolicy、DiscardOldestPolicy。
//拒绝执行接口
public interface RejectedExecutionHandler {
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
2、Arrays.sort();
Arrays.sort(T[] a, Comparator<? super T> c); 可以由用户自己提供排序策略,也是策略模式的应用
public static <T> void sort(T[] a, Comparator<? super T> c) {
//如果用户没提供排序策略, 则使用默认策略
if (c == null) {
sort(a);
} else {
if (LegacyMergeSort.userRequested)
legacyMergeSort(a, c);
else
TimSort.sort(a, 0, a.length, c, null, 0, 0);
}
}
四: 优缺点与作用场景
1、优缺点
1、优点
- 符合开闭原则, 添加新的策略只需要再写一个实现抽象策略类的实现类就行,不需要修改原来的代码。
- 避免使用多重if -else 语句块,充分体现面对对象设计思想。
2、缺点
- 使用者必须知道所有的策略跟具体的策略逻辑,自行选择需要的策略。
- 使用策略模式将产生很多的具体策略类,会导致类的数量过多。
2、作用场景
- 需要动态在几个算法中选择一种使用,并且各个算法都完全独立。
- 多重if-else语句, 可以将各个语句块中的代码移入具体策略类中,这样可以避免多重if-else语句。
- 多个类之间只有表现行为不同,可以使用策略模式, 在运行时动态选择要行为。
五:总结
策略模式仅封装算法,方便新算法插入系统中、老算法从系统中退休。在上面分析策略模式的缺点时提到,策略模式并不决定在何时使用何种算法,算法选择由客户端来决定,虽然这在一定程度上提高了系统的灵活性,但客户端需要理解所有具体策略类之间的区别,以便选择合适的算法,增加了客户端的使用难度。
因此我们可以引入工厂模式(上面 项目使用示例 已演示)