策略模式是工作中比较常用的设计模式之一,将一些除了过程不同其他都一样的函数封装成策略,然后调用方自己去选择想让数据执行什么过程策略,简单的说:一个类的行为或其算法可以在运行时更改。
介绍
主要解决
在有多种算法相似的情况下,使用 if...else 所带来的复杂和难以维护。
何时使用
一个系统有许多许多类,而区分它们的只是他们直接的行为。
如何解决
将这些算法封装成一个一个的类,任意地替换。
应用实例
- 诸葛亮的锦囊妙计,每一个锦囊就是一个策略。
- 旅行的出游方式,选择骑自行车、坐汽车,每一种旅行方式都是一个策略。
- JAVA AWT 中的 LayoutManager。
优点
- 算法可以自由切换。
- 避免使用多重条件判断。
- 扩展性良好。
缺点
- 策略类会增多。
- 所有策略类都需要对外暴露。
使用场景
- 如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
- 一个系统需要动态地在几种算法中选择一种。
- 如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。
注意事项:如果一个系统的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题。
经典策略模式
抽象策略接口
public interface Strategy {
public int doOperation(int num1, int num2);
}
实现策略类
public class OperationAdd implements Strategy{
@Override
public int doOperation(int num1, int num2) {
return num1 + num2;
}
}
public class OperationSubtract implements Strategy{
@Override
public int doOperation(int num1, int num2) {
return num1 - num2;
}
}
public class OperationMultiply implements Strategy{
@Override
public int doOperation(int num1, int num2) {
return num1 * num2;
}
}
创建 Context 类
public class Context {
private Strategy strategy;
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
public int executeStrategy(List<Stock> source) {
return strategy.doOperation(source);
}
}
使用 Context 来查看当它改变策略 Strategy 时的行为变化。
public int execute(String type) {
// 创建上下文
Context context = new Context();
// 这里选择策略
switch (type) {
case "add":
context.setStrategy(new OperationAdd());
break;
case "subtract":
context.setStrategy(new OperationSubtract());
break;
case "multiply":
context.setStrategy(new OperationMultiply());
break;
default:
throw new IllegalArgumentException("rankType not found");
}
// 然后执行策略
return context.executeStrategy(4, 4);
}
一个接口、三个策略类,还是比较啰嗦的。每新增一个策略类还要修改实例(可以使用抽象工厂解决,但是复杂度又上升了)。
基于枚举的策略模式
枚举策略类
public enum OperationEnum implements Strategy {
add {
@Override
public int doOperation(int num1, int num2) {
return num1 + num2;
}
},
subtract {
@Override
public int doOperation(int num1, int num2) {
return num1 - num2;
}
},
multiply {
@Override
public int doOperation(int num1, int num2) {
return num1 * num2;
}
};
// 这里定义了策略接口,可以选择使用抽象方法、也可以选择实现接口的方式
// public abstract int doOperation(int num1, int num2);
}
调用类
public int execute(String type) {
// 获取策略,这里如果未匹配会抛 IllegalArgumentException异常
OperationEnum operation = OperationEnum.valueOf(type);
// 然后执行策略
return operation.doOperation(4, 4);
}
如果策略简单的话,基于枚举的策略模式简单了很多,调用方也也不用修改,但如果想要正确地使用枚举策略模式需要额外考虑以下几点。
- 枚举的策略类是公用且静态,这意味着这个策略过程不能引入非静态的部分,扩展性受限
- 策略模式的目标之一,是优秀的扩展性和可维护性,最好能新增或修改某一策略类时,对其他类是无改动的。而枚举策略如果过多或者过程复杂,维护是比较困难的,可维护性受限
基于工厂的策略模式
为了更好的解决系统的扩展性和可维护性,推荐使用 spring 自带 beanFactory 的优势,实现一个基于工厂的策略模式。策略类改动只是添加 @Service 注解,并指定了 Service 的 value 属性即可。
@Service("add")
public class OperationAdd implements Strategy {
@Override
public int doOperation(int num1, int num2) {
return num1 + num2;
}
}
@Service("multiply")
public class OperationMultiply implements Strategy{
@Override
public int doOperation(int num1, int num2) {
return num1 * num2;
}
}
@Service("subtract")
public class OperationSubtract implements Strategy{
@Override
public int doOperation(int num1, int num2) {
return num1 - num2;
}
}
调用类借助 spring 工厂特性,完成策略类
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class AppDemo {
/**
* 利用注解 @Resource 和 @Autowired 特性,直接获取所有策略类
* <p>
* key = @Service的value
*/
@Resource
private Map<String, Strategy> operationMap;
@Test
public void demo() {
String type = "add";
// 获得策略实例
Strategy strategy = operationMap.get(type);
// 执行策略
int result = strategy.doOperation(4, 4);
System.out.println(result);
}
}
工厂策略模式比枚举策略模式繁琐一些,但也更加灵活、易扩展性和易维护。
简单策略推荐使用枚举策略模式,复杂策略推荐工厂策略模式。