本文的重点在于解释事情中所利用的设计模式,为了能够更好的理解设计模式,首先大略先容一下业务场景。利用设计模式,可以简化代码、提高扩展性、可掩护性和复用性。有哪些设计模式,这里就不再先容了,网上很多,本文只先容所用到设计模式。
一 线路检讨工具1 意义
为什么须要线路检讨工具呢,有以下几个方面的缘故原由:

为什么要把线路检讨工具产品化呢,考虑如下:
以前每次大匆匆,都是技能同学现场编写代码捞数据给到业务同学,而且由于职员流动性较大,代码可复用性较低,导致重复劳动。产品化后,可以方便地传承下去,避免不必要的重复劳动。每次由于韶光紧急,现场写的代码都比较大略,常常是直接将数据打印到标准输出,然后复制出来,人工拆分转成Excel格式;这样的过程要进行多次,占用太多技能同学的韶光。产品化后,解放了技能同学,业务同学可以自己在页面操作。很多数据检讨,是每次大匆匆都会进行的,业务同学与技能同学之间来回沟通的本钱较高。产品化后,可以避免这些耗时耗力的沟通,大家可以把更多的韶光放在其他的大匆匆保障事情上。2 检讨项
根据2020年D11进行的数据检讨,本次共实现8项,下面列举了4项,如下:
时效对齐检讨:确保履行分单正常。弱控线路与表达网络同等性:确保履行和路由不会由于线路缺失落而卡单。资源映射和编码映射同等:前者是表达线路时所用,后者是订单如约时所用,确保表达和如约能够同等。检讨线路数量:统计现存线路的情形。好了,理解了背景知识,下面开始先容所用到的设计模式,以及为什么要用、怎么用。
二 设计模式1 模板方法模式+泛型
上述8项数据检讨工具,大致的处理流程是类似的,如下:
针对不同的检讨工具,只有“线路数据检讨”这一步是不一样的逻辑,其他步骤都是相同的,如果每个检讨工具都实现这么一套逻辑,必定造成大量的重复代码,掩护性较差。
模板方法模式能够很好地办理这个问题。模板方法设计模式包含模板方法和基本方法,模板方法包含了紧张流程;基本方法是流程中共用的逻辑,如创建检讨任务,结果输出等等。
下图是所实现的模板方法模式的类继续关系:
剖析如下:
1)DataCheckProductService接口为对外供应的做事,dataCheck方法为统一的数据检讨接口。
2)AbstractDataCheckProductService是一个抽象类,设定模板,即在dataCheck方法中设定好流程,包括如下:
commonParamCheck方法:进行参数合法性检讨,不合法的情形下,直接返回。createFileName方法:创建文件名称。createTaskRecord方法:创建检讨任务。runDataCheck方法:进行数据检讨,这是一个抽象方法,所有检讨工具都要实现此方法,以实现自己的逻辑。uploadToOSS方法:将检讨结果上传到OSS,便于下载。updateRouteTask方法:结束时更新任务为完成。dataCheck方法为模板方法,runDataCheck方法由各个子类去实现,其他方法是基本方法。还有一些其他方法,是各个检讨工具都须要利用的,以是就放在了父类中。
3)CheckSupplierAndCodeMappingService类、CheckLandingCoverAreaService类和CheckAncPathNoServiceService类为详细的检讨工具子类,必须实现runDataCheck方法
由于不同检讨项检的查结果的格式是不一样的,以是利用了泛型,使得可以兼容不同的检讨结果。
简化的代码如下:
/ 数据检讨工具产品化对外做事接口 @author xxx @since xxx /public interface DataCheckProductService { / 数据检讨 @param requestDTO 要求参数 / < T> BaseResult< Long> dataCheck(DataCheckRequestDTO requestDTO);}/ 数据检讨工具产品化做事 @author xxx @since xxx /public abstract class AbstractDataCheckProductService implements DataCheckProductService { / 数据检讨 @param requestDTO 要求参数 @return / @Override public < T> BaseResult< Long> dataCheck(DataCheckRequestDTO requestDTO){ try{ //1. 参数合法性检讨 Pair< Boolean,String> paramCheckResult = commonParamCheck(requestDTO); if(!paramCheckResult.getLeft()){ return BaseResult.ofFail(paramCheckResult.getRight()); } //2. 创建导出任务 String fileName = createFileName(requestDTO); RouteTaskRecordDO taskRecordDO = createTaskRecord(fileName, requestDTO.getUserName()); //3. 进行数据检讨 List< T> resultList = Collections.synchronizedList(new ArrayList<>()); runDataCheck(resultList, requestDTO); //4. 写入文件 String ossUrl = uploadToOSS(fileName,resultList); //5. 更新任务为完成状态 updateRouteTask(taskRecordDO.getId(),DDportTaskStatus.FINISHED.intValue(), resultList.size()-1,"",ossUrl); return BaseResult.ofSuccess(taskRecordDO.getId()); }catch (Exception e){ LogPrinter.errorLog("dataCheck-error, beanName="+getBeanName(),e); return BaseResult.ofFail(e.getMessage()); } } / 进行数据检讨 @param resultList 存放检讨结果 @param requestDTO 要求参数 @return / public abstract < T> void runDataCheck(List< T> resultList, DataCheckRequestDTO requestDTO);}/ 检讨资源映射和编码映射同等 @author xxx @since xxx /public class CheckSupplierAndCodeMappingService extends AbstractDataCheckProductService{ @Override public < T> void runDataCheck(List< T> resultList, DataCheckRequestDTO requestDTO){ //自己的检讨逻辑 }}/ 检讨区域内落地配必须三级全覆盖 @author xxx @since xxx /public class CheckLandingCoverAreaService extends AbstractDataCheckProductService{ @Override public < T> void runDataCheck(List< T> resultList, DataCheckRequestDTO requestDTO){ //自己的检讨逻辑 }}/ 检讨资源映射和编码映射同等 @author xxx @since xxx /public class CheckAncPathNoServiceService extends AbstractDataCheckProductService{ @Override public < T> void runDataCheck(List< T> resultList, DataCheckRequestDTO requestDTO){ //自己的检讨逻辑 }}
利用模板方法模式的好处是:
简化了代码,每个工具只须要关心自己的核心检讨逻辑,不须要关注前置和后置操作。提高了扩展性,可以方便地增加新的检讨工具。统一的非常捕获和处理逻辑,子类有非常,只管往外抛出。2 策略模式
之以是会用到策略模式,是由于一些检讨工具写完之后,创造跑出来的结果数据太多,有几万、几十万等等,一方面,检讨比较耗时,结果文件会很大,下载耗时;另一方面,这么多数据给到业务同学,他们也很难处理和剖析,大概他们只是想看一下总体情形,并不想看详细的到哪个地方的线路。为此,在原来方案设计的根本上,增加了“统计信息”的选项,让用户可以自行选择“详细信息”还是“统计信息”,对应到页面上便是一个单选框,如下:
现在增加了一种检讨办法,今后是否还会有其他的检讨办法?完备有可能的。以是得考虑到扩展性,便于后面同学增加新的检讨办法。
此外,还有一种场景也可以利用策略模式,那便是业务系统中有很多业务网络,不同网络之间有一些差异;本次所实现的检讨工具,有几个涉及到多个网络,今后可能会涉及到所有网络。
综合以上两种场景,最得当的便是策略模式了。“详细信息”和“统计信息”各采取一种策略,不同网络利用不同的策略,既便于代码理解,又便于后续扩展。
“详细信息”和“统计信息”两种检讨结果的策略类图如下:
解析:
CompareModeStrategy对外供应统一的结果处理接口doHandle,策略子类必须实现此接口。SupplierAndCodeMappingStatisticsStrategy和SupplierAndCodeMappingDetailStrategy是检讨配送资源和编码映射同等性的两种结果信息办法,前者为统计办法,后者为详细办法。LandingCoverAreaStatisticsStrategy和LandingCoverAreaDetailStrategy是检讨落地配覆盖范围的两种结果信息办法,前者为统计办法,后者为详细办法。那AbstractCompareModeStrategy是干什么用的?它是一个抽象类,卖力承接所有策略子类共用的一些方法。简化的代码如下:
/ 检讨结果策略对外接口 @author xxx @since xxx /public interface CompareModeStrategy { / 详细操作 @param list @param requestDTO @return 结果集 / < T> List< T> doHandle(List< CompareBO> list, DataCheckRequestDTO requestDTO);}/ 策略公共父类 @author xxx @since xxx @apiNote 紧张是将子类共用方法和成员抽离出来 /public abstract class AbstractCompareModeStrategy implements CompareModeStrategy { //子类的共用方法,可以放在此类中}/ 检讨落地配覆盖范围 详细信息 策略类 @author xxx @since xxx /public class LandingCoverAreaDetailStrategy extends AbstractCompareModeStrategy{ @Override public < T> List< T> doHandle(List< CompareBO> list, DataCheckRequestDTO requestDTO){ List< T> resultList = new ArrayList<>(); //检讨结果处理逻辑 return resultList; }}/ 检讨落地配覆盖范围 统计信息 策略类 @author xxx @since xxx /public class LandingCoverAreaStatisticsStrategy extends AbstractCompareModeStrategy{ @Override public < T> List< T> doHandle(List< CompareBO> list, DataCheckRequestDTO requestDTO){ List< T> resultList = new ArrayList<>(); //检讨结果处理逻辑 return resultList; }}/ 检讨配送资源和编码映射同等 详细信息 策略类 @author xxx @since xxx /public class SupplierAndCodeMappingDetailStrategy extends AbstractCompareModeStrategy{ @Override public < T> List< T> doHandle(List< CompareBO> list, DataCheckRequestDTO requestDTO){ List< T> resultList = new ArrayList<>(); //检讨结果处理逻辑 return resultList; }}/ 检讨配送资源和编码映射同等 统计信息 策略类 @author xxx @since xxx /public class SupplierAndCodeMappingStatisticsStrategy extends AbstractCompareModeStrategy{ @Override public < T> List< T> doHandle(List< CompareBO> list, DataCheckRequestDTO requestDTO){ List< T> resultList = new ArrayList<>(); //检讨结果处理逻辑 return resultList; }}
同样,不同网络的处理策略类图如下:
代码与上面类似,就不展示了。
利用策略模式的好处是:
提高代码扩展性,后续增加别的结果格式或别的网络处理逻辑,可以在不修正其他策略的情形下直接新增。提高代码可读性,取代了if...else,条理清晰。不同系列采取不同的策略,策略与策略之间可以嵌套利用,形成策略的叠加效用。3 工厂模式
工厂模式办理的是bean的生产问题,大略工厂模式根据入参生产不同的bean,普通工厂模式针对每个bean都构建一个工厂,此两者各有利害,看须要。本方案采取的是大略工厂模式。
之以是利用工厂模式,是由于有太多的bean须要布局,如果在业务逻辑中布局各种bean,则会显得缭乱和分散,以是须要一个统一天生bean的地方,更好地管理和扩展。
本方案中紧张有三类bean须要工厂来天生:
模板方法模式中所用到的子类。检讨结果格式策略中所用到的子类。不同网络处理策略中所用到的子类。以是,利用三个工厂分别布局这三种类型的bean。其余,由于每个bean紧张的功能都在方法中,不涉及类变量的利用,以是可以利用spring容器天生的bean,而不是我们自己new出来,这样就使得bean可以重复利用。因此,这里的工厂只是bean的决策(根据参数决定利用哪个bean),不用自己new了。
三个工厂分别如下:
DataCheckProductFatory:由getDataCheckProductService方法根据输入参数决策利用哪个数据检讨工具。CompareModeStrategyFactory:用于决策利用哪种格式输出,由于将输出策略分为了2类(详细信息和统计信息),以是须要传入两个参数才能决定利用哪种策略。DataCheckNetworkStrategyFactory:用于决策利用哪种网络处理策略,由于将策略分为了2类(4PL网络和其他网络),以是须要传入两个参数才能决定利用哪种策略。这三个工厂的代码类似,这里就以CompareModeStrategyFactory为例,简化的代码如下:
/ 比对结果集办法 @author xxx @since xxx /@Servicepublic class CompareModeStrategyFactory { / 详细结果的bean / @Resource private LandingCoverAreaDetailStrategy landingCoverAreaDetailStrategy; @Resource private SupplierAndCodeMappingDetailStrategy supplierAndCodeMappingDetailStrategy; / 统计结果的bean / @Resource private LandingCoverAreaStatisticsStrategy landingCoverAreaStatisticsStrategy; @Resource private SupplierAndCodeMappingStatisticsStrategy supplierAndCodeMappingStatisticsStrategy; / 获取比对结果的策略 / public CompareModeStrategy getCompareModeStrategy(DataCheckProductEnum productEnum, DataCompareModeEnum modeEnum){ CompareModeStrategy compareModeStrategy = null; switch (modeEnum){ case DETAIL_INFO: compareModeStrategy = getDetailCompareModeStrategy(productEnum); break; case STATISTICS_INFO : compareModeStrategy = getStatisticsCompareModeStrategy(productEnum); break; default:; } return compareModeStrategy; } / 获取 信息信息 策略工具 / private CompareModeStrategy getDetailCompareModeStrategy(DataCheckProductEnum productEnum){ CompareModeStrategy compareModeStrategy = null; switch (productEnum){ case CHECK_LANDING_COVER_AREA: compareModeStrategy = landingCoverAreaDetailStrategy; break; case CHECK_SUPPLIER_AND_CODE_MAPPING: compareModeStrategy = supplierAndCodeMappingDetailStrategy; break; default:; } return compareModeStrategy; } / 获取 统计信息 策略工具 / private CompareModeStrategy getStatisticsCompareModeStrategy(DataCheckProductEnum productEnum){ CompareModeStrategy compareModeStrategy = null; switch (productEnum){ case CHECK_LANDING_COVER_AREA: compareModeStrategy = landingCoverAreaStatisticsStrategy; break; case CHECK_SUPPLIER_AND_CODE_MAPPING: compareModeStrategy = supplierAndCodeMappingStatisticsStrategy; break; default:; } return compareModeStrategy; }}
利用工厂模式的好处是:
便于bean的管理,所有的bean都在一处创建(或决策)。条理清晰,便于阅读和掩护。4 “代理模式”
这个代理模式是打着双引号的,由于不是真正的代理模式,只是从实现办法上来说,具有代理模式的意思。为什么须要用到代理模式?是由于类太多了,业务逻辑分散在各个类中,有的在模板子类中,有的在网络策略中,有的在结果输出格式策略中,而这些业务逻辑都须要多线程实行和非常捕获。如果有个代理类,能够收口这些处理逻辑,只需增加前置多线程处理和后置非常处理即可。
Java措辞中的函数式编程,具备这种能力。所谓函数式编程,是指能够将方法当做参数通报给方法,前面“方法”是业务逻辑,后面“方法”是代理,将业务逻辑通报给代理,就实现了统一收口的目的。
能够实现此功能的接口有四个,分别是:Consumer、Supplier、Predicate与Function,怎么利用可以网上查询。本方案利用的是Consumer,由于它是用来消费的,即须要传入一个参数,没有返回值,符合本方案的设计。
简化后的代码如下:
@Servicepublic class CheckLandingCoverAreaService extends AbstractDataCheckProductService { @Override public < T> void runDataCheck(List< T> resultList, DataCheckRequestDTO requestDTO){ dataCheckUtils.parallelCheckByFromResCodes(requestDTO,requestDTO.getFromResCodeList(),fromResCode->{ ExpressNetworkQuery query = new ExpressNetworkQuery(); query.setNs(NssEnum.PUB.getId()); query.setStatus(StatusEnum.ENABLE.getId()); query.setGroupNameList(requestDTO.getGroupNameList()); query.setBrandCodeList(requestDTO.getBrandCodeList()); query.setFromResCode(fromResCode); List< TmsMasterExpressNetworkDO> masterExpressNetworkDOS = tmsMasterExpressNetworkService.queryExpressNetworkTimeList(query); startCompareWithAnc(resultList,requestDTO,masterExpressNetworkDOS,fromResCode,solutionCodeMap); }); }}@Servicepublic class DataCheckUtils { / 并行处理每个仓 @param requestDTO 要求参数 @param fromResCodeList 须要检讨的仓列表 @param checkOperation 详细的业务处理逻辑 / public < T> void parallelCheckByFromResCodes(DataCheckRequestDTO requestDTO, List< String> fromResCodeList, Consumer< String> checkOperation){ List< CompletableFuture> futureList = Collections.synchronizedList(new ArrayList<>()); fromResCodeList.forEach(fromResCode->{ CompletableFuture future = CompletableFuture.runAsync(() -> { try{ checkOperation.accept(fromResCode); }catch (Exception e){ LogPrinter.errorLog("parallelCheckByFromResCodes-error, taskId="+requestDTO.getTaskId(),e); recordErrorInfo(requestDTO.getTaskId(),e); } }, DATA_CHECK_THREAD_POOL); futureList.add(future); }); //等待所有线程结束 futureList.forEach(future->{ try{ future.get(); }catch (Exception e){ LogPrinter.errorLog("parallelCheckByFromResCodes-future-get-error",e); } }); }}
可以看出,Consumer所代表的便是一个方法,将此方法作为parallelCheckByFromResCodes方法的一个参数,在parallelCheckByFromResCodes中进行多线程和非常处理,既能统一收口,又大大减少了重复代码。
代理模式的好处是:
统一收口多种不同的业务逻辑,统一做日志和非常处理。减少重复代码,提高了代码质量。可掩护性较强。5 其他
像结果输出格式策略模式那样,虽然AbstractCompareModeStrategy没有实际的业务逻辑,但仍旧把它作为一个基类,目的是所有子类共用的逻辑或方法,能够放在此类中,减少代码量,提升掩护性。
但是有的时候,不是继续自同一个基类的子类们,仍旧要共用一些逻辑或方法(如parallelCheckByFromResCodes方法),但Java措辞限定一个类只能继续一个基类,怎么办呢?大略的办法便是把这些共用逻辑或方法放到一个工具类(如DataCheckUtils)中。
三 思考&感悟在做这个项目的过程中,刚开始没有很好的设计,也没有想的很全面,导致代码改了又改,虽然延误点韶光,但以为是值得的。总结以下几点:
将提升代码可读性、可扩展性和可掩护性的意识注入到平时的项目中,便于自己,利于他人。如果项目紧急没韶光考虑很多,希望之后有韶光时能够改进和优化。事情不仅是为了完成任务,更是提升自己的过程。能力要用将来进行时。作者 | 兴亮
原文链接:http://click.aliyun.com/m/1000286002/
本文为阿里云原创内容,未经许可不得转载。