在项目中看到茫茫多的if else,相信每一个有追求的程序员都会有优化的欲望。策略模式就是一种很好的优化途径。刚好最近在项目中实践了一次策略模式,不敢说是最佳实践,但也算是个人的一次实践经验分享。场景如下:
业务需要将网络上来源不同的数据文件解析,然后将解析得到的数据插入数据库。那么代码在这里主要做3件事情:
1、解析数据,得到数据实体对象POJO1;
2、将得到的数据实体转换成方便插入数据库的数据实体POJO2;
3、将POJO2插入数据库。
由于公司代码无法共享,本文使用伪代码说明:
优化前大概是这样的:
if (来源A) 解析数据A 转换实体A1-A2 else if (来源B) 解析数据B1-B2 转换实体B else if(...) ...... else .... ....... if (来源A) 实体A1迭代插入数据库 else if (来源B) 是实体B2迭代插入数据库 else if (...) ...... else ......
上面的代码显然很不优雅,虽然是做相同的事情,却写了这么一大堆的if else,后续如果新增来源,还得增加,很不利于维护。这里我们可以实现一个策略接口Stragtegy,然后根据业务(这里是不同的数据来源)创建其实现类StragtegyImplA,StragtegyImplB.......
//F, T泛型分别代表转换前和转换后的POJO类型 interface Strategy<F, T> { //通过来源source解析数据 //实现类根据业务实现从不同的数据库解析,从excel解析等 List<F> doParseData(source); // 数据转换 //通过泛型F,T表示转换前后的POJO,减少if else判断 List<T> transferPOJO(List<F> fromPOJOs); //保存数据 //实现类根据业务实现保存不同的数据库,或者保存到文件 saveData(List<T> toPOJOs); }
实现类这里就省略了,相信不影响大家理解思路,这样我们就把变化的部分都封装到了不同的策略实现类里面StragtegyImplA,StragtegyImplB.......,那么问题来了,在原来调用的地方应该怎么知道该创建哪个类型的策略实现类呢?方法有很多,我这里用了枚举配合工厂方法实现。将文件来源source保存在枚举类Enum中,通过在工厂类中判断枚举类型,返回不同的策略实现类。实际上,根据业务需要,还可以增加一个上下文类Context,但这里的业务场景还不需要这么高的封装程度,暂时省略这个类。
优化后是这样的:
Strategy strategyImpl = StrategyFactory.createStrategyImpl(各种数据来源) List<F> fromList = strategyImpl.doParseData(...); List<T> toList = strategyimpl.tranferPOJO(fromList); saveData(toList);
烦人的if else没了,看起来果然清爽很多。但是代码看起来还是很繁琐,这里我们再次审视一次业务需要做的三件事情
1、解析数据,得到数据实体对象POJO1;
2、将得到的数据实体转换成方便插入数据库的数据实体POJO2;
3、将POJO2插入数据库。
对照接口的三个方法,其实第1、2个方法解析数据和转换POJO返回的结果都属于“中间数据”,都是为了第3个方法保存数据库使用的,这里我们就可以通过在实现saveData的时候这样做
public XXXStrategyImpl implements Strategy<F,T> { ..... saveData(List<T> toList) { doParseData(...); transferPOJO(...); .........(这里才真正开始保存业务的逻辑) }
这样就更简洁了,当然也是建立在具体的业务上,实践需要结合自身的业务场景。
策略模式缺点也总结一下:
1、在策略类型超过大概5种时,策略模式对调用者代码阅读水平要求就比较高了,调用者必须理解各个策略的算法才能更好地使用
2、if else减少了,但是类却增多了,也是一个需要权衡自身情况的tradeoff吧
3、策略必须暴露给调用者,违反迪米特法则。