代码清洁怪胎!在课堂上看到几十个if-else是不是很疯狂?设计模式学了就不会用吗?面试的时候问你,你只能回答最简单的singlecaseMode,我问你有没有用过反射等高级特性,你回答了吗?图片来自Pexels这次让设计模式(模板方法模式+工厂模式)和反射帮你消除if-else!真是对中超发展有用的干货!一、那个陷阱某天,码农FatRoller接到了上级的一个请求。这个要求真棒。一站式智能报表查询平台,支持MySQL、Pgxl、TiDB、Hive、Presto、Mongo等多种数据源,您可以查询和展示您想要的所有数据,对业务人员的数据分析具有重要意义!虽然每个数据源的参数校验、查询引擎、查询逻辑都不同,但是滚肥猪这些框架她都非常熟悉,这对她来说不是问题。她只花了一天的时间就把它们全部写完了。为首的庞滚雄也对庞滚柱的效率给予了肯定。但好景不长。第三天,leader闲来无事,准备做CodeReview,结果愣住了。一个类中有将近30个if-else代码。该死的,这会让Thecodefreak崩溃。//检查输入参数的合法性Booleancheck=false;if(DataSourceEnum.hive.equals(dataSource)){check=checkHiveParams(params);}elseif(DataSourceEnum.tidb.equals(dataSource)){check=checkTidbParams(params);}elseif(DataSourceEnum.mysql.equals(dataSource)){check=checkMysqlParams(params);}//elseif.......省略pgxl,presto等if(check){if(DataSourceEnum.hive.equals(dataSource)){list=queryHive(params);}elseif(DataSourceEnum.tidb.equals(dataSource)){list=queryTidb(params);}elseif(DataSourceEnum.mysql.equals(dataSource)){list=queryMysql(params);}//elseif.......省略pgxl,presto等}//记录日志log.info("User={}查询数据源={}结果大小={}",params.getUserName(),参数.getDataSource(),list.size());2、模板模式来救场首先我们来分析一下,不管是什么数据源,算法结构(过程)都是一样的:检查参数查询记录日志的有效性这不就说明模板是一样,但是具体细节不一样吧?我们先看一下设计模式中模板方法模式的定义:不改变算法结构的算法。通俗地说,就是把子类的相同方法放到它的抽象父类中。我们的需求不是类似于模板方法模式吗?所以我们可以将模板提取到父类(抽象类)中。至于具体步骤的实现,这些特殊步骤可以通过子类重写。废话不多说,先写父模板吧。完全相同的逻辑是记录日志。这一步应该写在模板里。至于测试参数和查询,这两个方法是不同的,所以需要设置为抽象方法,由子类重写。publicabstractclassAbstractDataSourceProcesser{publicListquery(Tparams){Listlist=newArrayList<>();//校验参数有效性不同引擎sql校验逻辑不同Booleanb=checkParam(params);if(booleanb=checkParam(params);if(b){//Querylist=queryData(params);}//记录日志log.info("User={}querydatasource={}resultsize={}",params.getUserName(),params.getDataSource(),列表大小());returnlist;}//抽象方法由子类实现具体逻辑abstractBooleancheckParam(Tparams);abstractListqueryData(Tparams);}这段代码很简单。但是为了照顾新手,我还是要说明一点:T这个东西。之所以叫泛型,是因为不同数据源的输入参数不同,所以我们用泛型。但它们也有共同的参数,比如用户名。因此,为了不重复冗余,更好的利用公共资源和标准输入参数,我们可以在泛型设计中有一个泛型上限:publicclassQueryInputDomain{publicStringuserName;//查询用户名publicStringdataSource;//查询数据源比如mysql\tidb等publicTparams;//具体参数一般不同数据源参数不同}publicclassMysqlQueryInputextendsQueryInputDomain{privateStringdatabase;//数据库publicStringsql;//sql}然后就轮到子类出现了,通过上面分析起来其实很简单,只是继承了父类,重写了checkParam()和queryData()方法。下面以MySQL数据源为例,其他数据源同理:@Component("dataSourceProcessor#mysql")publicclassMysqlProcesserextendsAbstractDataSourceProcesser{@OverridepublicBooleancheckParam(MysqlQueryInputparams){System.out.println("检查是否mysql参数准确");returntrue;}@OverridepublicListqueryData(MysqlQueryInputparams){Listlist=newArrayList<>();System.out.println("开始查询mysql数据");returnlist;}这样一来,所有的数据源都是自成体系的,都有一个只属于自己的类。以后扩展数据源或者修改某个数据源的逻辑都非常方便清晰。说实话,模板方法模式太简单了,抽象类太基础太普通了,大部分应届生都会知道。但是对于初入职场的新人来说,要果断应用到实际生产中,确实很难。因此,在此提醒大家:一定要有抽象思维,避免冗余代码重复。还有,重复几句,即使是工作了几年的工程师也很容易出错。就是把思考限制在今天的需要上。比如老大一开始只给你一个MySQL数据源查询需求,根本没有if-else。可能你不会放在心上,直接写死在一个类里,不考虑后续扩展。直到后来新的需求越来越多,你才突然意识到,你必须重构所有东西,那是在浪费你的时间。因此,我想提醒大家:不要把自己的需求局限在今天,而要考虑未来。从一开始就实现了高扩展性,后续的需求变更和维护都非常爽。3、工厂模型来救场#Phone类:手机标准规范类(AbstractProduct)publicinterfacePhone{voidmake();}#MiPhone类:制造小米手机(Product1)publicclassMiPhoneimplementsPhone{publicMiPhone(){this.make();}@Overridepublicvoidmake(){System.out.println("makexiaomiphone!");}}#iPhone类:制造苹果手机(Product2)publicclassIPhoneimplementsPhone{publicIPhone(){this.make();}@Overridepublicvoidmake(){System.out.println("makeiphone!");}}但是模板模式还是没有完全解决胖滚猪的if-else,因为需要根据dataSource参数判断是哪个服务实现了查询逻辑passedin.现在是这样写的:那么这种if-else应该如何消除呢?我想先给大家讲讲工厂模式的故事。工厂模式:工厂方法模式是一种创建对象的模式,广泛应用于jdk和Spring以及Struts框架中。它将创建对象的工作转移到工厂类。为了呼应“工厂”二字,我举一个代工工厂的例子让大家了解一下,让大家印象更深一些。以手机制造为例。我们知道有苹果手机、小米手机等,每个品牌手机的制造方法肯定是不一样的。我们可以先为手机定义一个标准接口。这个接口有一个make()方法,然后不同类型的手机继承这个接口:#Phone类:手机标准规范类(AbstractProduct)publicinterfacePhone{voidmake();}#MiPhone类:制造小米手机(Product1)publicclassMiPhoneimplementsPhone{publicMiPhone(){this.make();}@Overridepublicvoidmake(){System.out.println("makexiaomiphone!");}}#iPhone类:制造苹果手机(Product2);}}现在有一家手机代工厂:【天霸手机代工厂】。客户只会告诉工厂手机的型号,需要匹配不同型号的生产计划,那么代工厂是如何实现的呢?其实也很简单,简单工厂模型(也有抽象工厂模型和工厂方法模型,有兴趣的可以了解)是这样实现的:#PhoneFactory类:手机代工厂(Factory)publicclassPhoneFactory{publicPhonemakePhone(StringphoneType){if(phoneType.equalsIgnoreCase("MiPhone")){returnnewMiPhone();}elseif(phoneType.equalsIgnoreCase("iPhone")){returnnewIPhone();}}}这样,如果客户告诉你手机的型号,可以调用foundry类的方法获取对应的手机制造类。你会发现其实无非就是if-else,只是把if-else抽取出来一个工厂类,工厂类统一创建对象,不会侵入我们的业务代码,在维护和维护上会好很多美学。首先,我们应该为每个具体的dataSourceProcessor(数据源执行器)添加Spring容器注解@Component,比如MysqlProcesser、TidbProcesser。我认为我不需要对注释进行更多解释。重点是:我们可以把不同的数据源做成相似的bean名称,比如dataSourceProcessor#数据源名称,下面两段代码:@Component("dataSourceProcessor#mysql")tidb")publicclassTidbProcesserextendsAbstractDataSourceProcesser{这样做有什么好处?我可以用Spring帮我们一次性加载所有继承自AbstractDataSourceMapProcesser的Bean名称,如下而Value则是对应的Bean:@ServicepublicclassQueryDataServiceImplimplementsQueryDataService{@ResourcepublicMapdataSourceProcesserMap;publicstaticStringbeanPrefix="dataSourceProcessor#";@OverridepublicListqueryData(QueryInputDomaindomain){AbstractDataSourceProcesserdataSourceProcesser=dataSourceProcesserMap.get(beanPrefix+domain.getDataSource());//省略查询代码}}可能你还是不太明白,我们来看看运行效果直接:①dataSourceProcesserMap的内容如下,里面存放了所有的数据源bean,Key是Bean的名字,Value是对应的Bean:②我只需要传key(即前缀+数据源名=beanName)就可以匹配对应的actuator。例如,当参数dataSource为tidb时,key为dataSourceProcessor#tidb。TidbProcesser可以根据key直接从dataSourceProcesserMap中获取:4.反射保存字段或者直接看代码:publicstaticStringclassPrefix="com.lyl.java.advance.service.";AbstractDataSourceProcessersourceGenerator=(AbstractDataSourceProcesser)Class.forName(classPrefix+DataSourceEnum.getClasszByCode(domain.getDataSource())).newInstance();需要注意的是,这个方法是通过className获取类的实例的,前端肯定不会传className的。因此可以使用枚举类定义不同数据源的类名:publicenumDataSourceEnum{mysql("mysql","MysqlProcesser"),tidb("tidb","TidbProcesser");privateStringcode;privateStringclassz;5.一些总结童鞋们总觉得设计模式没什么用,因为平时写代码不仅CRUD还要CRUD。面试的时候被问到设计模式,只能回答最简单的单例模式,问有没有用过反射等高级特性。答案是否定的。其实Java中的23种设计模式,每一种都是经典。今天我们使用模板方法模式+工厂模式(或反射)来解决if-else的崩溃。在后续的设计模式学习中,也要多加练习,多从实际项目中找地方用,才能真正把知识据为己有。虽然本文的内容和技术要点很简单,但旨在告诉你,你应该有一个很好的代码抽象思维。杜绝代码中一堆if-else或者其他不好的代码。即使你有很好的代码抽象思维,在做需求开发的时候,也不要局限于现在,只考虑现在,多考虑未来的扩展性。就像谈恋爱的时候,只想着现在的男人是没心没肺的人,考虑到未来的才是有责任心的人,“愿世上没有没心没肺的男人”!作者:刘艳玲编辑:陶佳龙来源:转载自微信公众号胖滚猪学习编程(ID:bdstar_lyl)