游戏电视苹果数码历史美丽
投稿投诉
美丽时装
彩妆资讯
历史明星
乐活安卓
数码常识
驾车健康
苹果问答
网络发型
电视车载
室内电影
游戏科学
音乐整形

干掉重复代码,这三种方式绝了

  软件工程师和码农最大的区别就是平时写代码时习惯问题,码农很喜欢写重复代码而软件工程师会利用各种技巧去干掉重复的冗余代码。
  业务同学抱怨业务开发没有技术含量,用不到设计模式、Java高级特性、OOP,平时写代码都在堆CRUD,个人成长无从谈起。
  其实,我认为不是这样的。设计模式、OOP是前辈们在大型项目中积累下来的经验,通过这些方法论来改善大型项目的可维护性。反射、注解、泛型等高级特性在框架中大量使用的原因是,框架往往需要以同一套算法来应对不同的数据结构,而这些特性可以帮助减少重复代码,提升项目可维护性。
  在我看来,可维护性是大型项目成熟度的一个重要指标,而提升可维护性非常重要的一个手段就是减少代码重复。那为什么这样说呢?如果多处重复代码实现完全相同的功能,很容易修改一处忘记修改另一处,造成Bug有一些代码并不是完全重复,而是相似度很高,修改这些类似的代码容易改(复制粘贴)错,把原本有区别的地方改为了一样。
  今天,我就从业务代码中最常见的三个需求展开,聊聊如何使用Java中的一些高级特性、设计模式,以及一些工具消除重复代码,才能既优雅又高端。通过今天的学习,也希望改变你对业务代码没有技术含量的看法。1。利用工厂模式模板方法模式,消除ifelse和重复代码
  假设要开发一个购物车下单的功能,针对不同用户进行不同处理:普通用户需要收取运费,运费是商品价格的10,无商品折扣;VIP用户同样需要收取商品价格10的快递费,但购买两件以上相同商品时,第三件开始享受一定折扣;内部用户可以免运费,无商品折扣。
  我们的目标是实现三种类型的购物车业务逻辑,把入参Map对象(Key是商品ID,Value是商品数量),转换为出参购物车类型Cart。
  先实现针对普通用户的购物车处理逻辑:购物车DatapublicclassCart{商品清单privateListItemitemsnewArrayList();总优惠privateBigDecimaltotalDiscount;商品总价privateBigDecimaltotalItemPrice;总运费privateBigDecimaltotalDeliveryPrice;应付总价privateBigDecimalpayPrice;}购物车中的商品DatapublicclassItem{商品IDprivatelongid;商品数量privateintquantity;商品单价privateBigDecimalprice;商品优惠privateBigDecimalcouponPrice;商品运费privateBigDecimaldeliveryPrice;}普通用户购物车处理publicclassNormalUserCart{publicCartprocess(longuserId,MapLong,Integeritems){CartcartnewCart();把Map的购物车转换为Item列表ListItemitemListnewArrayList();items。entrySet()。stream()。forEach(entry{ItemitemnewItem();item。setId(entry。getKey());item。setPrice(Db。getItemPrice(entry。getKey()));item。setQuantity(entry。getValue());itemList。add(item);});cart。setItems(itemList);处理运费和商品优惠itemList。stream()。forEach(item{运费为商品总价的10item。setDeliveryPrice(item。getPrice()。multiply(BigDecimal。valueOf(item。getQuantity()))。multiply(newBigDecimal(0。1)));无优惠item。setCouponPrice(BigDecimal。ZERO);});计算商品总价cart。setTotalItemPrice(cart。getItems()。stream()。map(itemitem。getPrice()。multiply(BigDecimal。valueOf(item。getQuantity())))。reduce(BigDecimal。ZERO,BigDecimal::add));计算运费总价cart。setTotalDeliveryPrice(cart。getItems()。stream()。map(Item::getDeliveryPrice)。reduce(BigDecimal。ZERO,BigDecimal::add));计算总优惠cart。setTotalDiscount(cart。getItems()。stream()。map(Item::getCouponPrice)。reduce(BigDecimal。ZERO,BigDecimal::add));应付总价商品总价运费总价总优惠cart。setPayPrice(cart。getTotalItemPrice()。add(cart。getTotalDeliveryPrice())。subtract(cart。getTotalDiscount()));returncart;}}
  然后实现针对VIP用户的购物车逻辑。与普通用户购物车逻辑的不同在于,VIP用户能享受同类商品多买的折扣。所以,这部分代码只需要额外处理多买折扣部分:publicclassVipUserCart{publicCartprocess(longuserId,MapLong,Integeritems){。。。itemList。stream()。forEach(item{运费为商品总价的10item。setDeliveryPrice(item。getPrice()。multiply(BigDecimal。valueOf(item。getQuantity()))。multiply(newBigDecimal(0。1)));购买两件以上相同商品,第三件开始享受一定折扣if(item。getQuantity()2){item。setCouponPrice(item。getPrice()。multiply(BigDecimal。valueOf(100Db。getUserCouponPercent(userId))。pide(newBigDecimal(100)))。multiply(BigDecimal。valueOf(item。getQuantity()2)));}else{item。setCouponPrice(BigDecimal。ZERO);}});。。。returncart;}}
  最后是免运费、无折扣的内部用户,同样只是处理商品折扣和运费时的逻辑差异:publicclassInternalUserCart{publicCartprocess(longuserId,MapLong,Integeritems){。。。itemList。stream()。forEach(item{免运费item。setDeliveryPrice(BigDecimal。ZERO);无优惠item。setCouponPrice(BigDecimal。ZERO);});。。。returncart;}}
  对比一下代码量可以发现,三种购物车70的代码是重复的。原因很简单,虽然不同类型用户计算运费和优惠的方式不同,但整个购物车的初始化、统计总价、总运费、总优惠和支付价格的逻辑都是一样的。
  正如我们开始时提到的,代码重复本身不可怕,可怕的是漏改或改错。比如,写VIP用户购物车的同学发现商品总价计算有Bug,不应该是把所有Item的price加在一起,而是应该把所有Item的pricequantity加在一起。
  这时,他可能会只修改VIP用户购物车的代码,而忽略了普通用户、内部用户的购物车中,重复的逻辑实现也有相同的Bug。
  有了三个购物车后,我们就需要根据不同的用户类型使用不同的购物车了。如下代码所示,使用三个if实现不同类型用户调用不同购物车的process方法:GetMapping(wrong)publicCartwrong(RequestParam(userId)intuserId){根据用户ID获得用户类型StringuserCategoryDb。getUserCategory(userId);普通用户处理逻辑if(userCategory。equals(Normal)){NormalUserCartnormalUserCartnewNormalUserCart();returnnormalUserCart。process(userId,items);}VIP用户处理逻辑if(userCategory。equals(Vip)){VipUserCartvipUserCartnewVipUserCart();returnvipUserCart。process(userId,items);}内部用户处理逻辑if(userCategory。equals(Internal)){InternalUserCartinternalUserCartnewInternalUserCart();returninternalUserCart。process(userId,items);}returnnull;}
  电商的营销玩法是多样的,以后势必还会有更多用户类型,需要更多的购物车。我们就只能不断增加更多的购物车类,一遍一遍地写重复的购物车逻辑、写更多的if逻辑吗?
  当然不是,相同的代码应该只在一处出现!
  如果我们熟记抽象类和抽象方法的定义的话,这时或许就会想到,是否可以把重复的逻辑定义在抽象类中,三个购物车只要分别实现不同的那份逻辑呢?
  其实,这个模式就是模板方法模式。我们在父类中实现了购物车处理的流程模板,然后把需要特殊处理的地方留空白也就是留抽象方法定义,让子类去实现其中的逻辑。由于父类的逻辑不完整无法单独工作,因此需要定义为抽象类。
  如下代码所示,AbstractCart抽象类实现了购物车通用的逻辑,额外定义了两个抽象方法让子类去实现。其中,processCouponPrice方法用于计算商品折扣,processDeliveryPrice方法用于计算运费。publicabstractclassAbstractCart{处理购物车的大量重复逻辑在父类实现publicCartprocess(longuserId,MapLong,Integeritems){CartcartnewCart();ListItemitemListnewArrayList();items。entrySet()。stream()。forEach(entry{ItemitemnewItem();item。setId(entry。getKey());item。setPrice(Db。getItemPrice(entry。getKey()));item。setQuantity(entry。getValue());itemList。add(item);});cart。setItems(itemList);让子类处理每一个商品的优惠itemList。stream()。forEach(item{processCouponPrice(userId,item);processDeliveryPrice(userId,item);});计算商品总价cart。setTotalItemPrice(cart。getItems()。stream()。map(itemitem。getPrice()。multiply(BigDecimal。valueOf(item。getQuantity())))。reduce(BigDecimal。ZERO,BigDecimal::add));计算总运费cart。setTotalDeliveryPrice(cart。getItems()。stream()。map(Item::getDeliveryPrice)。reduce(BigDecimal。ZERO,BigDecimal::add));计算总折扣cart。setTotalDiscount(cart。getItems()。stream()。map(Item::getCouponPrice)。reduce(BigDecimal。ZERO,BigDecimal::add));计算应付价格cart。setPayPrice(cart。getTotalItemPrice()。add(cart。getTotalDeliveryPrice())。subtract(cart。getTotalDiscount()));returncart;}处理商品优惠的逻辑留给子类实现protectedabstractvoidprocessCouponPrice(longuserId,Itemitem);处理配送费的逻辑留给子类实现protectedabstractvoidprocessDeliveryPrice(longuserId,Itemitem);}
  有了这个抽象类,三个子类的实现就非常简单了。普通用户的购物车NormalUserCart,实现的是0优惠和10运费的逻辑:Service(valueNormalUserCart)publicclassNormalUserCartextendsAbstractCart{OverrideprotectedvoidprocessCouponPrice(longuserId,Itemitem){item。setCouponPrice(BigDecimal。ZERO);}OverrideprotectedvoidprocessDeliveryPrice(longuserId,Itemitem){item。setDeliveryPrice(item。getPrice()。multiply(BigDecimal。valueOf(item。getQuantity()))。multiply(newBigDecimal(0。1)));}}
  VIP用户的购物车VipUserCart,直接继承了NormalUserCart,只需要修改多买优惠策略:Service(valueVipUserCart)publicclassVipUserCartextendsNormalUserCart{OverrideprotectedvoidprocessCouponPrice(longuserId,Itemitem){if(item。getQuantity()2){item。setCouponPrice(item。getPrice()。multiply(BigDecimal。valueOf(100Db。getUserCouponPercent(userId))。pide(newBigDecimal(100)))。multiply(BigDecimal。valueOf(item。getQuantity()2)));}else{item。setCouponPrice(BigDecimal。ZERO);}}}
  内部用户购物车InternalUserCart是最简单的,直接设置0运费和0折扣即可:Service(valueInternalUserCart)publicclassInternalUserCartextendsAbstractCart{OverrideprotectedvoidprocessCouponPrice(longuserId,Itemitem){item。setCouponPrice(BigDecimal。ZERO);}OverrideprotectedvoidprocessDeliveryPrice(longuserId,Itemitem){item。setDeliveryPrice(BigDecimal。ZERO);}}
  抽象类和三个子类的实现关系图,如下所示:
  图片
  是不是比三个独立的购物车程序简单了很多呢?接下来,我们再看看如何能避免三个if逻辑。
  或许你已经注意到了,定义三个购物车子类时,我们在Service注解中对Bean进行了命名。既然三个购物车都叫XXXUserCart,那我们就可以把用户类型字符串拼接UserCart构成购物车Bean的名称,然后利用Spring的IoC容器,通过Bean的名称直接获取到AbstractCart,调用其process方法即可实现通用。
  其实,这就是工厂模式,只不过是借助Spring容器实现罢了:GetMapping(right)publicCartright(RequestParam(userId)intuserId){StringuserCategoryDb。getUserCategory(userId);AbstractCartcart(AbstractCart)applicationContext。getBean(userCategoryUserCart);returncart。process(userId,items);}
  试想,之后如果有了新的用户类型、新的用户逻辑,是不是完全不用对代码做任何修改,只要新增一个XXXUserCart类继承AbstractCart,实现特殊的优惠和运费处理逻辑就可以了?
  这样一来,我们就利用工厂模式模板方法模式,不仅消除了重复代码,还避免了修改既有代码的风险。这就是设计模式中的开闭原则:对修改关闭,对扩展开放。2。利用注解反射消除重复代码
  是不是有点兴奋了,业务代码居然也能OOP了。我们再看一个三方接口的调用案例,同样也是一个普通的业务逻辑。
  假设银行提供了一些API接口,对参数的序列化有点特殊,不使用JSON,而是需要我们把参数依次拼在一起构成一个大字符串。按照银行提供的API文档的顺序,把所有参数构成定长的数据,然后拼接在一起作为整个字符串。因为每一种参数都有固定长度,未达到长度时需要做填充处理:
  字符串类型的参数不满长度部分需要以下划线右填充,也就是字符串内容靠左;数字类型的参数不满长度部分以0左填充,也就是实际数字靠右;货币类型的表示需要把金额向下舍入2位到分,以分为单位,作为数字类型同样进行左填充。对所有参数做MD5操作作为签名(为了方便理解,Demo中不涉及加盐处理)。
  比如,创建用户方法和支付方法的定义是这样的:
  图片
  代码很容易实现,直接根据接口定义实现填充操作、加签名、请求调用操作即可:publicclassBankService{创建用户方法publicstaticStringcreateUser(Stringname,Stringidentity,Stringmobile,intage)throwsIOException{StringBuilderstringBuildernewStringBuilder();字符串靠左,多余的地方填充stringBuilder。append(String。format(10s,name)。replace(,));字符串靠左,多余的地方填充stringBuilder。append(String。format(18s,identity)。replace(,));数字靠右,多余的地方用0填充stringBuilder。append(String。format(05d,age));字符串靠左,多余的地方用填充stringBuilder。append(String。format(11s,mobile)。replace(,));最后加上MD5作为签名stringBuilder。append(DigestUtils。md2Hex(stringBuilder。toString()));returnRequest。Post(http:localhost:45678reflectionbankcreateUser)。bodyString(stringBuilder。toString(),ContentType。APPLICATIONJSON)。execute()。returnContent()。asString();}支付方法publicstaticStringpay(longuserId,BigDecimalamount)throwsIOException{StringBuilderstringBuildernewStringBuilder();数字靠右,多余的地方用0填充stringBuilder。append(String。format(020d,userId));金额向下舍入2位到分,以分为单位,作为数字靠右,多余的地方用0填充stringBuilder。append(String。format(010d,amount。setScale(2,RoundingMode。DOWN)。multiply(newBigDecimal(100))。longValue()));最后加上MD5作为签名stringBuilder。append(DigestUtils。md2Hex(stringBuilder。toString()));returnRequest。Post(http:localhost:45678reflectionbankpay)。bodyString(stringBuilder。toString(),ContentType。APPLICATIONJSON)。execute()。returnContent()。asString();}}
  可以看到,这段代码的重复粒度更细:三种标准数据类型的处理逻辑有重复,稍有不慎就会出现Bug;处理流程中字符串拼接、加签和发请求的逻辑,在所有方法重复;实际方法的入参的参数类型和顺序,不一定和接口要求一致,容易出错;代码层面针对每一个参数硬编码,无法清晰地进行核对,如果参数达到几十个、上百个,出错的概率极大。
  那应该如何改造这段代码呢?没错,就是要用注解和反射!
  使用注解和反射这两个武器,就可以针对银行请求的所有逻辑均使用一套代码实现,不会出现任何重复。
  要实现接口逻辑和逻辑实现的剥离,首先需要以POJO类(只有属性没有任何业务逻辑的数据类)的方式定义所有的接口参数。比如,下面这个创建用户API的参数:DatapublicclassCreateUserAPI{privateStringname;privateStringidentity;privateStringmobile;privateintage;}
  有了接口参数定义,我们就能通过自定义注解为接口和所有参数增加一些元数据。如下所示,我们定义一个接口API的注解BankAPI,包含接口URL地址和接口说明:Retention(RetentionPolicy。RUNTIME)Target(ElementType。TYPE)DocumentedInheritedpublicinterfaceBankAPI{Stringdesc()default;Stringurl()default;}
  然后,我们再定义一个自定义注解BankAPIField,用于描述接口的每一个字段规范,包含参数的次序、类型和长度三个属性:Retention(RetentionPolicy。RUNTIME)Target(ElementType。FIELD)DocumentedInheritedpublicinterfaceBankAPIField{intorder()default1;intlength()default1;Stringtype()default;}
  接下来,注解就可以发挥威力了。
  如下所示,我们定义了CreateUserAPI类描述创建用户接口的信息,通过为接口增加BankAPI注解,来补充接口的URL和描述等元数据;通过为每一个字段增加BankAPIField注解,来补充参数的顺序、类型和长度等元数据:BankAPI(urlbankcreateUser,desc创建用户接口)DatapublicclassCreateUserAPIextendsAbstractAPI{BankAPIField(order1,typeS,length10)privateStringname;BankAPIField(order2,typeS,length18)privateStringidentity;BankAPIField(order4,typeS,length11)注意这里的order需要按照API表格中的顺序privateStringmobile;BankAPIField(order3,typeN,length5)privateintage;}
  另一个PayAPI类也是类似的实现:BankAPI(urlbankpay,desc支付接口)DatapublicclassPayAPIextendsAbstractAPI{BankAPIField(order1,typeN,length20)privatelonguserId;BankAPIField(order2,typeM,length10)privateBigDecimalamount;}
  这2个类继承的AbstractAPI类是一个空实现,因为这个案例中的接口并没有公共数据可以抽象放到基类
  通过这2个类,我们可以在几秒钟内完成和API清单表格的核对。理论上,如果我们的核心翻译过程(也就是把注解和接口API序列化为请求需要的字符串的过程)没问题,只要注解和表格一致,API请求的翻译就不会有任何问题。
  以上,我们通过注解实现了对API参数的描述。接下来,我们再看看反射如何配合注解实现动态的接口参数组装:第3行代码中,我们从类上获得了BankAPI注解,然后拿到其URL属性,后续进行远程调用。第69行代码,使用stream快速实现了获取类中所有带BankAPIField注解的字段,并把字段按order属性排序,然后设置私有字段反射可访问。第1238行代码,实现了反射获取注解的值,然后根据BankAPIField拿到的参数类型,按照三种标准进行格式化,将所有参数的格式化逻辑集中在了这一处。第4148行代码,实现了参数加签和请求调用。privatestaticStringremoteCall(AbstractAPIapi)throwsIOException{从BankAPI注解获取请求地址BankAPIbankAPIapi。getClass()。getAnnotation(BankAPI。class);bankAPI。url();StringBuilderstringBuildernewStringBuilder();Arrays。stream(api。getClass()。getDeclaredFields())获得所有字段。filter(fieldfield。isAnnotationPresent(BankAPIField。class))查找标记了注解的字段。sorted(Comparator。comparingInt(aa。getAnnotation(BankAPIField。class)。order()))根据注解中的order对字段排序。peek(fieldfield。setAccessible(true))设置可以访问私有字段。forEach(field{获得注解BankAPIFieldbankAPIFieldfield。getAnnotation(BankAPIField。class);Objectvalue;try{反射获取字段值valuefield。get(api);}catch(IllegalAccessExceptione){e。printStackTrace();}根据字段类型以正确的填充方式格式化字符串switch(bankAPIField。type()){caseS:{stringBuilder。append(String。format(bankAPIField。length()s,value。toString())。replace(,));break;}caseN:{stringBuilder。append(String。format(bankAPIField。length()s,value。toString())。replace(,0));break;}caseM:{if(!(valueinstanceofBigDecimal))thrownewRuntimeException(String。format({}的{}必须是BigDecimal,api,field));stringBuilder。append(String。format(0bankAPIField。length()d,((BigDecimal)value)。setScale(2,RoundingMode。DOWN)。multiply(newBigDecimal(100))。longValue()));break;}default:break;}});签名逻辑stringBuilder。append(DigestUtils。md2Hex(stringBuilder。toString()));StringparamstringBuilder。toString();longbeginSystem。currentTimeMillis();发请求StringresultRequest。Post(http:localhost:45678reflectionbankAPI。url())。bodyString(param,ContentType。APPLICATIONJSON)。execute()。returnContent()。asString();log。info(调用银行API{}url:{}参数:{}耗时:{}ms,bankAPI。desc(),bankAPI。url(),param,System。currentTimeMillis()begin);returnresult;}
  可以看到,所有处理参数排序、填充、加签、请求调用的核心逻辑,都汇聚在了remoteCall方法中。有了这个核心方法,BankService中每一个接口的实现就非常简单了,只是参数的组装,然后调用remoteCall即可。创建用户方法publicstaticStringcreateUser(Stringname,Stringidentity,Stringmobile,intage)throwsIOException{CreateUserAPIcreateUserAPInewCreateUserAPI();createUserAPI。setName(name);createUserAPI。setIdentity(identity);createUserAPI。setAge(age);createUserAPI。setMobile(mobile);returnremoteCall(createUserAPI);}支付方法publicstaticStringpay(longuserId,BigDecimalamount)throwsIOException{PayAPIpayAPInewPayAPI();payAPI。setUserId(userId);payAPI。setAmount(amount);returnremoteCall(payAPI);}
  其实,许多涉及类结构性的通用处理,都可以按照这个模式来减少重复代码。
  反射给予了我们在不知晓类结构的时候,按照固定的逻辑处理类的成员;而注解给了我们为这些成员补充元数据的能力,使得我们利用反射实现通用逻辑的时候,可以从外部获得更多我们关心的数据。3。利用属性拷贝工具消除重复代码
  最后,我们再来看一种业务代码中经常出现的代码逻辑,实体之间的转换复制。
  对于三层架构的系统,考虑到层之间的解耦隔离以及每一层对数据的不同需求,通常每一层都会有自己的POJO作为数据实体。比如,数据访问层的实体一般叫作DataObject或DO,业务逻辑层的实体一般叫作Domain,表现层的实体一般叫作DataTransferObject或DTO。
  这里我们需要注意的是,如果手动写这些实体之间的赋值代码,同样容易出错。
  对于复杂的业务系统,实体有几十甚至几百个属性也很正常。就比如ComplicatedOrderDTO这个数据传输对象,描述的是一个订单中的几十个属性。如果我们要把这个DTO转换为一个类似的DO,复制其中大部分的字段,然后把数据入库,势必需要进行很多属性映射赋值操作。就像这样,密密麻麻的代码是不是已经让你头晕了?ComplicatedOrderDTOorderDTOnewComplicatedOrderDTO();ComplicatedOrderDOorderDOnewComplicatedOrderDO();orderDO。setAcceptDate(orderDTO。getAcceptDate());orderDO。setAddress(orderDTO。getAddress());orderDO。setAddressId(orderDTO。getAddressId());orderDO。setCancelable(orderDTO。isCancelable());orderDO。setCommentable(orderDTO。isComplainable());属性错误orderDO。setComplainable(orderDTO。isCommentable());属性错误orderDO。setCancelable(orderDTO。isCancelable());orderDO。setCouponAmount(orderDTO。getCouponAmount());orderDO。setCouponId(orderDTO。getCouponId());orderDO。setCreateDate(orderDTO。getCreateDate());orderDO。setDirectCancelable(orderDTO。isDirectCancelable());orderDO。setDeliverDate(orderDTO。getDeliverDate());orderDO。setDeliverGroup(orderDTO。getDeliverGroup());orderDO。setDeliverGroupOrderStatus(orderDTO。getDeliverGroupOrderStatus());orderDO。setDeliverMethod(orderDTO。getDeliverMethod());orderDO。setDeliverPrice(orderDTO。getDeliverPrice());orderDO。setDeliveryManId(orderDTO。getDeliveryManId());orderDO。setDeliveryManMobile(orderDO。getDeliveryManMobile());对象错误
  如果不是代码中有注释,你能看出其中的诸多问题吗?
  如果原始的DTO有100个字段,我们需要复制90个字段到DO中,保留10个不赋值,最后应该如何校验正确性呢?数数吗?即使数出有90行代码,也不一定正确,因为属性可能重复赋值。
  有的时候字段命名相近,比如complainable和commentable,容易搞反(第7和第8行),或者对两个目标字段重复赋值相同的来源字段(比如第28行)
  明明要把DTO的值赋值到DO中,却在set的时候从DO自己取值(比如第20行),导致赋值无效。
  这段代码并不是我随手写出来的,而是一个真实案例。有位同学就像代码中那样把经纬度赋值反了,因为落库的字段实在太多了。这个Bug很久都没发现,直到真正用到数据库中的经纬度做计算时,才发现一直以来都存错了。
  修改方法很简单,可以使用类似BeanUtils这种Mapping工具来做Bean的转换,copyProperties方法还允许我们提供需要忽略的属性:ComplicatedOrderDTOorderDTOnewComplicatedOrderDTO();ComplicatedOrderDOorderDOnewComplicatedOrderDO();BeanUtils。copyProperties(orderDTO,orderDO,id);returnorderDO;总结
  第一种代码重复是,有多个并行的类实现相似的代码逻辑。我们可以考虑提取相同逻辑在父类中实现,差异逻辑通过抽象方法留给子类实现。使用类似的模板方法把相同的流程和逻辑固定成模板,保留差异的同时尽可能避免代码重复。同时,可以使用Spring的IoC特性注入相应的子类,来避免实例化子类时的大量ifelse代码。
  第二种代码重复是,使用硬编码的方式重复实现相同的数据处理算法。我们可以考虑把规则转换为自定义注解,作为元数据对类或对字段、方法进行描述,然后通过反射动态读取这些元数据、字段或调用方法,实现规则参数和规则定义的分离。也就是说,把变化的部分也就是规则的参数放入注解,规则的定义统一处理。
  第三种代码重复是,业务代码中常见的DO、DTO、VO转换时大量字段的手动赋值,遇到有上百个属性的复杂类型,非常非常容易出错。我的建议是,不要手动进行赋值,考虑使用Bean映射工具进行。此外,还可以考虑采用单元测试对所有字段进行赋值正确性校验。

美丽是什么一般咱们普通人觉的看着顺眼,穿着舒服就是美了,但人家偏偏有另一种眼光去理解。这是美吗?这是什么美?又美在哪呢?首先说穿衣方面,少之又少,衣不遮体,也不实用看着也不太……1699元起,华为nova10青春版正式开售一亿像素质感人像IT之家3月6日消息,华为今日宣布,华为nova10青春版臻彩直屏手机将于3月6日10:08在华为商城及各大授权电商全面开售,新品限时优惠100元,首发价1699元起。华……湖北钟祥黄仙洞奇观黄仙洞俗称黄金洞,是国家级大洪山风景名胜区的核心景点。位为大洪山脉之南麓,坐落在钟祥境内,距郢中镇66公里。《大洪山志》卷五形胜篇载曰:洞之山为黄仙山,相传黄石公憩此,故……千亿大项目股东落定!大唐不再控股?中国核电2月24日发布公告称,中国核能电力股份有限公司(以下简称中国核电)拟与中国大唐集团核电有限公司(以下简称大唐核电)、大连市国有资本管理运营有限公司(以下简称大连国资)共……荷乙集锦海牙VS赫拉克勒GBSPORTS推荐Hercule,也被称为AlmeiroHercules,上赛季在Erepisie中排名第16位。在保级附加赛中不敌SBVElite,不幸降入荷甲。本赛季荷乙……海底捞研究报告管理架构梳理完毕,业绩反转之年(报告出品方作者:国泰君安证券,刘越男,陈京,宋小寒)1。公司管理体系梳理完毕,初现成效海底捞此前核心问题在于,前端过快的开店遇上不利的外部环境,后端公司治理难以匹配前端……整个北方都在变得越来越像东三省,房价阴跌你有没有发现,整个北方都在变得越来越像东三省,甚至除了常州三角之外,全国都有东北化的趋势。第一:全民想入编制,宇宙的尽头是编制,官里官气的中年人特别受欢迎。第二:财……中詹密圈套了!不碰瓷乔丹,老詹生涯进不了前十?看不起中詹密圈套了!不碰瓷乔丹,老詹生涯进不了前十?看不起科比破乔丹得分纪录那一夜是在客场打森林狼,罚球破纪录之后官方叫了暂停,第一个上来祝贺的是两边的球员,然后裁判过来告诉他……爱尔兰女足主帅不明白为何进球被吹中国女足的冲吊有威胁直播吧2月23日讯热身赛00战平中国女足后,爱尔兰女足主帅保乌对于比赛进行了点评。谈论曼尼恩冲撞门将在先导致进球被吹我不知道裁判为什么会判罚无效。比赛中我唯一担心的……又轰下4898,他这样打下去,乔丹的NBA纪录不保了38岁的年龄对于绝大多数NBA球员而言都是已经退役或者濒临退役的年纪,NBA历史上只有屈指可数的球员在这个年纪还能打出明星级表现。乔丹就是其中之一,乔丹年满38岁后第二次复出,……1月全球手游下载量50亿次,SubwaySurfers地铁跑2月21日,SensorTower商店情报数据显示,Miniclip旗下SYBOGames《SubwaySurfers地铁跑酷》以近2300万次下载,位列1月全球移动游戏下载榜……全明星选人结束!詹姆斯摆双塔,约基奇情商高,字母哥又要输?全明星选人有变化一年一度的全明星拉开了帷幕,利拉德捧起了职业生涯第一座三分大赛冠军,麦克朗成功拯救了扣篮大赛,不过最让人关注的还是全明星正赛,或者说,全明星正赛前的选人环……
以色列研究人员德尔塔毒株或将卷土重来中新网5月4日电据新加坡《联合早报》报道,近日,以色列研究人员发现,在新冠变异毒株奥密克戎加速传播之际,德尔塔毒株仍在隐秘传播。研究人员警告,待奥密克戎毒株式微之时,德尔塔毒株……雷军确认,小米音质最好的手机停产了要问音质最好的手机是哪一款?小米10S绝对榜上有名,同时这也是小米生产的音质最好的手机,在2021年3月份的时候正式发布,同时也是小米10的升级版。小米10S搭载的是骁龙870……中老年健康早餐的黄金法则是什么?中老年应该遵循健康早餐的法则、从早做起!1:早餐的时间经过科学验证、最佳的早餐时间是早上7点到8点之间,因为这个时间段人的食欲是最旺盛的。早餐与中餐的间隔时间为45……17中4,20中7!山西输球揪出两大罪人,杨学增真是低估了郭在季后赛首战19分大胜广州男篮之后,本以为第二场比赛也会呈现一般倒的局势,但是广州男篮利用一场出色的防守,硬生生把比赛拖入第三场决战。从双方本场关键时刻的决战来看,山西男篮的准……温度背后还有物理史上的经典大案?著名物理学家理查德。费曼曾说:就算你知道一种鸟在所有语言中的名字,仍可能对其一无所知。我很早就明白知道一个东西的名字和真正的了解一个东西并不是一回事。国家三有保护动物戴胜……经典中的经典!视频里绝对看不到,许钟豪也有挨打的时候来几个经典的老图。这几个经典瞬间在视频里还真看不到。亲上了!不知道是不是真爱,丁彦雨航曾经是两届常规赛MVP得主,巅峰时那也是很厉害,一个人根本防不住,胡金秋只能用嘴防了……你是合格的熬夜者吗?做好熬夜的准备了吗?冬去夏来,天气越来越暖和了,一年一度的熬夜舒适季又来了,一大波熬夜者即将崛起!当下的我们经常觉得不熬夜,生活就缺点什么!晚上关掉灯,窝在被窝里,不停地滑动闪亮亮的手机,这才是我……1000至2000元档位,买什么手机,看这里全知道昨天又很多小伙伴说,关注了慢慢很久还是不知道卖什么手机。担心花了大价钱还买到智商机对不对。来今天慢慢就来诶大家整一个手机推荐系列。是时候来波全价位推荐了,今天是10……异地储户资金流入河南村镇银行,第三方平台成看不见的手暴雷后,我一度羞于跟人谈起,这种离奇的事跟人说了还要跟人解释一番‘互联网存款’之类的知识点,人家听了还会好奇我为什么会把钱存到偏远的乡村银行。河南银行暴雷后,家在湖北武汉、却在……北京4大富婆身价个个不菲,除赵燕陈丽华之外都有谁?如今,随着中国经济的快速发展,我国人民的生活水平也有着普遍提升。大家也逐渐地发现,自己身边的有钱人越来越多了。自己做生意起家的企业家们,不再是活在小说或者电视剧里的存在,……西藏民俗禁忌一一顺时针方向绕行藏族人忌讳逆时针方向或反方向绕行转经的原因在于如来所说:所有八十种微妙细相之一,回身顾视必皆右旋之细相和如来进退步履,皆中规矩,感得任运右旋之好。因此,佛教信徒很重视分清左右内……新疆重启慕士塔格峰攀登活动将迎来全国300多位登山队员记者从新疆登山协会获悉:6月中旬至8月中旬,位于阿克陶县的慕士塔格峰大本营将迎来全国300多位登山队员,暂停两年的慕士塔格峰攀登活动重启。慕士塔格峰被称为冰川之父。图片来……
友情链接:易事利快生活快传网聚热点七猫云快好知快百科中准网快好找文好找中准网快软网