通过策略模式消除冗长的if块

一、场景:

1
2
3
4
5
6
7
8
9
10
11
if (很复杂的条件A){
// 此处省略很复杂的逻辑
}else if (很复杂的条件B){
// 此处省略很复杂的逻辑
}else if (很复杂的条件C){
// 此处省略很复杂的逻辑
}else if (很复杂的条件D){
// 此处省略很复杂的逻辑
}else if (很复杂的条件E){
// 此处省略很复杂的逻辑
}

如上述代码,工作中经常遇到这种场景,导致代码的可读性较差。对后续代码的扩展维护来说,是巨大的灾难。

以上代码有以下几点问题:

  1. 后续增加任何功能,都需要在原来耦合的代码里添加代码,有可能会影响原有功能。

  2. 没有做到开闭原则,一段良好的代码需要做到对扩展开发,对修改关闭。

  3. 校验的逻辑都在一个类中,导致这个类中的代码很多,从而影响了代码的可读性、可维护性。

  4. if-else条件判断很难懂,无法判断某个条件中的校验到底是校验哪种校验类型,每次查看这段代码都要研究好久。

二、解决:

使用策略工厂模式来解决以上问题,把每种校验的方式封装起来,然后通过策略工厂模式来路由下发,把冗长的代码解耦出来,形成了一套框架,并且保证了代码的扩展性。

2.1 创建策略工厂类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class CheckStrategyFactory {
private final Map<CheckStrategySelector, InterfaceCheck> strategyRegistry = new HashMap<>();

@Autowired
public CheckStrategyFactory(ACheck aCheck, BCompare bCompare, CCheck cCheck, DCheck dCheck,
ASelector aSelector,BSelector bSelector,CSelector cSelector,DSelector dSelector) {

// 在构造函数或初始化块中注册所有策略
strategyRegistry.put(aSelector, aCheck);
strategyRegistry.put(bSelector, bCompare);
strategyRegistry.put(cSelector, cCheck);
strategyRegistry.put(dSelector, dCheck);
// ... 注册更多策略 ...
}

public InterfaceCheck getStrategy(MatcheA ma, MatcheB mb) {

for (Map.Entry<CheckStrategySelector, InterfaceCheck> entry : strategyRegistry.entrySet()) {
if (entry.getKey().matches(ma, mb)) {
return entry.getValue();
}
}

return null; // 兜底检查策略返回null
}
}

2.2 创建策略选择接口和校验接口

创建2个接口,一个策略选择接口CheckStrategySelector,一个校验接口InterfaceCheck。

1
2
3
public interface CheckStrategySelector {
boolean matches(MatcheA ma, MatcheB mb);
}
1
2
3
public interface InterfaceCheck {
CheckOutputModel check(CheckA ca, CheckB cb);
}

2.3 实现策略选择接口和校验接口

再创建4个策略类和4个校验类分别实现策略选择接口CheckStrategySelector和校验接口InterfaceCheck。

下面仅展示一个,其他省略。

策略选择实现类

1
2
3
4
5
6
7
public class ASelector implements CheckStrategySelector {
@Override
public boolean matches(MatcheA ma, MatcheB mb) {
// 根据参数判断是否符合该策略
return ma && mb;
}
}

校验实现类

1
2
3
4
5
6
7
@Service("aCheck")
public class ACheck implements InterfaceCheck {
@Override
public CheckOutputModel check(CheckA ca, CheckB cb) {
// 具体实现逻辑
}
}

2.4 改造if判断

最后“场景”章节中的if判断语句块,改造如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Service("commonCheckHandler")
public class CommonCheckHandler implements CheckHandler{
private final CheckStrategyFactory factory;

public CommonCheckHandler(CheckStrategyFactory factory) {
this.factory = factory;
}

@Override
public CheckOutputModel doHandle(A paramA, B paramB, C paramC) throws Exception {
CheckOutputModel result = new CheckOutputModel();

MatcheA ma = paramA.getMatcheA();
MatcheB mb = paramB.getMatcheB();

InterfaceCheck interfaceCheckStrategy = factory.getStrategy(ma, mb);
if(interfaceCheckStrategy != null){
return interfaceCheckStrategy.check(paramC.getCheckA(), paramC.getCheckB());
} else {
result.setSuccess(false);
result.setErrorCode("未找到对应的校验策略");
return result;
}
}
}

以上通过策略工厂模式把那段代码拆成了多个文件,通过策略工厂模式把冗长的if-else代码给分解了。

原作者的图片,结构参考

重构之后,创建了工厂类,由工厂类中的策略判断逻辑来决定是哪一种策略类型,在运行时动态确定使用哪种策略,最终路由到对应的校验方法里。

  1. 重构后的代码符合了开闭原则,添加新策略的时候,最小化、集中化代码改动、减少引入bug的风险。

  2. 重构后的代码解耦了之前代码的复杂度,解耦了策略的定义、创建和使用,控制代码复杂度,让每个部分的代码不至于太复杂、代码量过多。现在每个类的代码基本上在一显示屏就能展示完成。

  3. 增加了代码的可读性和可维护性。

三、总结

不是所有if-else分支都是烂代码,只要if-else分支不复杂,代码不多,这并没有问题,只要遵循KISS原则,怎么简单怎么来,就是最好的设计。

一旦if-else分支很多,且每个分支都包含很多复杂的逻辑判断,这个时候就可以考虑是不是通过策略模式可以更清晰的梳理代码,使得代码维护性更强。

1
2
3
4
5
6
本文为个人知识学习,非原创!非作者!如本博客有侵权行为,请与我联系。
摘录以下文章,内容根据个人需求有所删减,尊重知识产出,尊重作者知识劳动成果。

作者:阿里云开发者 汪峰(蔚风)
链接:https://mp.weixin.qq.com/s/tg4vTL6_TI-tnxaMyVLhsA
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。