前言
今天用策略模式优化了下之前的业务代码,重新温习了下设计模式的相关理念,在此记录一下
一、策略模式介绍
定义 :
策略模式定义了算法家族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户。 (摘自<大话设计模式>)
个人理解:
策略模式简单理解,应该是对于同一个业务功能,在不同的场景需求下提供不同的实现逻辑,来达到动态切换业务算法,满足不同场景的目的。同时它也有另外的好处,即优化代码结构,使其脱离大量逻辑判断,对外只提供 Context上下文,让算法与实际业务代码解耦,对使用者屏蔽底层实现逻辑。
策略模式 UML类图如下
二、具体使用场景
- 1、业务代码需要根据场景不同,切换不同的实现逻辑
- 2、代码中存在大量 if else 逻辑判断
1、举例
这边以我业务代码场景为例,不透露具体代码,举一个相似例子,如下:
Integer a = 1;
if (a == 1) {
// 业务逻辑处理...
} else if (a == 2) {
// 业务逻辑处理...
} else if (a == 3) {
// 业务逻辑处理...
} else if (a == 4) {
// 业务逻辑处理...
} else if (a == 5) {
// 业务逻辑处理...
} else if (a == 6) {
// 业务逻辑处理...
} else if (a == 7) {
// 业务逻辑处理...
} else {
// 业务逻辑处理...
}
在分支较少,只有两三个的时候,采用上述处理也并无不妥,但是一旦分支多了呢?下次再次新增一个实现方案呢?
这时候代码是不是就变得很囊肿了,让人看着头皮发麻。
2、传统方式优化
分析上述代码,发现可以存在几个优化点:
- if else 分支过多
- 逻辑处理算法与业务代码耦合
- 新增逻辑实现无法优雅扩展
按介绍里 策略模式的UML类图,传统的处理方式是把每一个逻辑处理都封装成一个类,再通过暴露 策略上下文 Context类给调用方使用。
这种方式有个弊端,就是造成的类代码过多,每一种策略都要新增一个实现类,当代码量过于庞大后,后来者也不易读懂。还有就是if else 的分支问题,传统方式处理后,只是把 if else 逻辑判断迁移到了Context 上下文里面,还是避免不了分支过多问题。
传统方式的优化代码,这里不过多介绍
3、Map + 函数式编程 优化
把所有的策略,存到了一个 Map里,通过对key来获取对应的实现方式来执行。value存的是lambda函数的形式。
优化后主要有两个类、
一个是Context 上下文,暴露给客户端调用,这里维护所有的策略。
一个是StrategyImpl ,里面提供所有的策略实际实现,一个策略就是一个方法。
Context上下文
代码示例如下
/**
* 策略上下文.
*
* @author linzp
* @date 2021/11/12 11:14
*/
@Slf4j
@Component
public class XxxxStrategyContext {
/**
* 存放所有策略.
*/
private Map<String, BiFunction<String, String, String>> strategyMap = new HashMap<>(16);
/**
* 具体的策略细节.
*/
@Autowired
private XxxxStrategyImpl xxxStrategy;
/**
* 加载所有策略.
*/
@PostConstruct
public void initStrategies() {
strategyMap.put("one", (arg1, arg2) -> xxxStrategy.doSomethingOne(arg1, arg2));
strategyMap.put("two", (arg1, arg2) -> xxxStrategy.doSomethingTwo(arg1, arg2));
strategyMap.put("three", (arg1, arg2) -> xxxStrategy.doSomethingThree(arg1, arg2));
strategyMap.put("four", (arg1, arg2) -> xxxStrategy.doSomethingFour(arg1, arg2));
strategyMap.put("five", (arg1, arg2) -> xxxStrategy.doSomethingFive(arg1, arg2));
}
/**
* 指派具体的策略去执行.
*
* @param arg1
* @param arg2
* @param key 根据key 来获取不同的策略,即上述的 one、two、three...
* @return
*/
public String doSomethingByStrategy(String arg1, String arg2, String key) {
BiFunction<String, String, String> biFunction = strategyMap.get(key);
if (Objects.isNull(biFunction)) {
// 没有找到特定的策略,则采用默认实现(一般只有加了新的类型,但是没有配置对应的处理策略才会走到这里)
log.warn("========= 没有配置该处理策略,采用默认实现,key {} ====== " + key);
return xxxStrategy.doSomethingDefault(arg1, arg2);
}
return biFunction.apply(arg1, arg2);
}
}
具体实现策略, XxxxStrategyImpl 类代码如下:
/**
* 所有策略具体实现.
*
* @author linzp
* @date 2021/11/12 14:57
*/
@Component
public class XxxxStrategyImpl {
/**
* 默认实现
*
* @param arg1
* @param arg2
* @return
*/
public String doSomethingDefault(arg1, arg2){
// 默认业务逻辑处理...
return "";
}
/**
* 策略1
*
* @param arg1
* @param arg2
* @return
*/
public String doSomethingOne(arg1, arg2){
// 业务逻辑处理 1...
return "";
}
/**
* 策略2
*
* @param arg1
* @param arg2
* @return
*/
public String doSomethingTwo(arg1, arg2){
// 业务逻辑处理 2...
return "";
}
/**
* 策略3
*
* @param arg1
* @param arg2
* @return
*/
public String doSomethingThree(arg1, arg2){
// 业务逻辑处理 3...
return "";
}
// 省略后续策略代码 ....
}
如此,便可消除所有分支判断,也仅用两个类,就完成了传统方式的策略实现。
总结
好的代码设计,可以让后续在面对不断变化的需求时,不必手忙脚乱,加班加点,也给后人的接手留一条活路 (狗头)。
码字不易~ ~ 如果帮到了你,点个赞啪~~