策略模式
What‘s the problem? 有什么问题
不言而喻,过多的if-else代码和嵌套,会使阅读代码的人很难理解到底是什么意思。尤其是那些没有注释的代码。
可扩展/维护性差,因为if-else特别多,想要新加一个条件的时候,就会很难添加,极其容易影响到其他的if分支。
这种代码写起来简单,但明显违反了面向对象的 2 个基本原则:
在《Java开发手册-华山版》的编程规约-控制语句中有讲:超过 3 层的 if-else 的逻辑判断代码可以使用卫语句、策略模式、状态模式等来实现。
什么是策略模式
定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。策略模式让算法独立于使用它的客户而变化。
用人话翻译一下就是,运行时我给你这个类的方法传不同的“key”,这个方法会执行不同的业务逻辑。这和 if else 有什么区别吗?
策略模式做了什么?
其实策略模式的核心思想和if else如出一辙,根据不同的key动态的找到不同的业务逻辑。那它就是如此吗?
实际上,我们口中的策略模式其实就是代码结构上调整,用接口+实现类+分派逻辑来使代码结构可维护性好点。
策略模式的类结构图
- Strategy(抽象策略):抽象策略类,并且定义策略执行入口
- ConcreteStrategy(具体策略):实现抽象策略,实现aglorithmInterface 方法
- Context(环境):运行特定的策略类。
Context上下文,起承上启下的作用,屏蔽高层模块对策略、算法的直接访问,封装可能存在的变化。
public class Context {
Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
//上下文接口
public void contextInterface() {
strategy.algorithmInterface();
}
}
抽象策略类,是对策略、算法的抽象,通常为接口,定义每个策略或算法必须具有的方法和属性。
public interface Strategy {
//算法方法
void algorithmInterface();
}
策略类的具体实现,抽象策略的实现过多时,就需要考虑使用混合模式来解决策略类膨胀的问题
public class ConcreteStrategyA implements Strategy {
@Override
public void algorithmInterface() {
System.out.println("算法A实现");
}
}
public class ConcreteStrategyB implements Strategy {
@Override
public void algorithmInterface() {
System.out.println("算法B实现");
}
}
小结,即使用了策略模式,该写的业务逻辑照常写,到逻辑分派的时候,动态替换策略实现类,来执行相应的逻辑。而它的优化点是抽象出了接口,将业务逻辑封装在一个一个的实现类,任意地替换。在复杂场景(业务逻辑较多)时比直接if else更方便维护扩展。
就几个if else场景,真的需要用到策略模式吗
三四行的业务逻辑,你让我定义一大堆类,有必要这么麻烦吗?看具体的业务逻辑还要点击到不同的类中,有没有简单点的?
策略模式的缺点
其实对于策略模式,让我们觉得麻烦的就是结构比if else复杂了(特别是对新手不“友好”):
- 策略实现类会增多
- 业务逻辑分散到不同的实现类中,而且没有一个地方可以俯视整个业务逻辑
针对传统策略模式的缺点,我们可以利用Map与Funtion<T,R>接口(Java8的特性)来实现。
代码编写的一般方式:
/**
* 某个业务服务类
*/
@Service
public class BizService {
/**
* 传统的 if else 解决方法
* 当每个业务逻辑有 3 4 行时,用传统的策略模式不值得,
* 直接的if else又显得不易读
*/
public String getCheckResult(String order) {
if ("校验1".equals(order)) {
return "执行业务逻辑1";
} else if ("校验2".equals(order)) {
return "执行业务逻辑2";
} else if ("校验3".equals(order)) {
return "执行业务逻辑3";
}
return "不在处理的逻辑中返回业务错误";
}
}
使用Map+Funtion<T,R>的方式:
/**
* 某个业务服务类
*/
@Service
public class BizService {
/**
* 业务逻辑分派Map
* Function为函数式接口,下面代码中 Function<String, String> 的含义
* 是接收一个Stirng类型的变量,返回一个String类型的结果
*/
private Map<String, Function<String, String>> checkResultDispatcher =
new HashMap<>();
/**
* 初始化 业务逻辑分派Map 其中value 存放的是 lambda表达式
*/
public void checkResultDispatcherInit() {
checkResultDispatcher.put("校验1",
order -> String.format("对%s执行业务逻辑1", order));
checkResultDispatcher.put("校验2",
order -> String.format("对%s执行业务逻辑2", order));
checkResultDispatcher.put("校验3",
order -> String.format("对%s执行业务逻辑3", order));
}
public String getCheckResult(String order) {
//从逻辑分派Dispatcher中获得业务逻辑代码,result变量是一段lambda表达式
Function<String, String> result = checkResultDispatcher.get(order);
if (result != null) {
//执行这段表达式获得String类型的结果
return result.apply(order);
}
return "不在处理的逻辑中返回业务错误";
}
}
调用代码
/**
* 模拟一次http调用
*/
@RestController
public class BizController {
@Autowired
private BizService bizService;
@PostMapping("/checkResult")
public String test(String order) {
return bizService.getCheckResult(order);
}
}
使用Map+Funtion方式之后的好处:
- 在一段代码里直观的看到"判断条件"与业务逻辑的映射关系
- 不需要单独定义接口与实现类,直接使用现有的函数式接口,而Funtion实现类直接就是业务代码本身。
总结
一、策略模式对代码结构做了什么优化?
抽象了出了接口,将业务逻辑封装成一个一个的实现类,任意地替换。在复杂场景(业务逻辑较多)时比直接 if else 来的好维护些。
二、使用缺点,就是几个if else场景我需要用到策略模式?!
传统接口方式的实现缺点: 1、策略类会很多; 2、业务逻辑分散到各个实现类中,而且没有一个地方可以俯视全局的业务逻辑。
三、Java8的特性帮助解决策略模式的痛点
针对传统策略模式的缺点,可以利用Map与Funtion函数式接口来实现的思路。