来源:捡田螺的小男孩
前言
日常开发中,我们经常会遇到一些重复冗余的代码 。大家都知道重复代码不好 ,它主要有这些缺点:可维护性差、可读性差、增加错误风险 等等。最近呢,我优化了一些系统中的重复代码,用了好几种的方式,感觉挺有用的。所以本文给大家讲讲优化重复冗余代码的几种方式~
抽取公用方法
抽个工具类
反射
泛型
继承和多态
设计模式
函数式Lambda
AOP切面
基于 Spring Boot + MyBatis Plus + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
1. 抽取公用方法
抽取公用方法 ,是最常用的代码去重方式 ~
比如这个例子,分别遍历names列表,然后各自转化为大写和小写打印出来:
publicclassTianLuoExample{ publicstaticvoidmain(String[]args){ Listnames=Arrays.asList("Alice","Bob","Charlie","David","TianLuo"); System.out.println("UppercaseNames:"); for(Stringname:names){ StringuppercaseName=name.toUpperCase(); System.out.println(uppercaseName); } System.out.println("LowercaseNames:"); for(Stringname:names){ StringlowercaseName=name.toLowerCase(); System.out.println(lowercaseName); } } }
显然,都是遍历names过程,代码是重复冗余的,只不过转化大小写不一样而已 。我们可以抽个公用方法 processNames,优化成这样:
publicclassTianLuoExample{ publicstaticvoidprocessNames(Listnames,Function nameProcessor,StringprocessType){ System.out.println(processType+"Names:"); for(Stringname:names){ StringprocessedName=nameProcessor.apply(name); System.out.println(processedName); } } publicstaticvoidmain(String[]args){ List names=Arrays.asList("Alice","Bob","Charlie","David","TianLuo"); processNames(names,String::toUpperCase,"Uppercase"); processNames(names,String::toLowerCase,"Lowercase"); } }
基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 实现的后台管理系统 + 用户小程序,支持 RBAC 动态权限、多租户、数据权限、工作流、三方登录、支付、短信、商城等功能
2. 抽工具类
我们优化重复代码,抽一个公用方法后,如果发现这个方法有更多共性,就可以把公用方法升级为一个工具类 。比如这样的业务场景:注册,修改邮箱,重置密码等,都需要校验邮箱
实现注册功能时,用户会填邮箱,需要验证邮箱格式 ,
publicclassRegisterServiceImplimplementsRegisterService{ privatestaticfinalStringEMAIL_REGEX= "^[A-Za-z0-9+_.-]+@(.+)$"; publicbooleanregisterUser(UserInfoRequserInfo){ Stringemail=userInfo.getEmail(); Patternpattern=Pattern.compile(EMAIL_REGEX); MatcheremailMatcher=pattern.matcher(email); if(!emailMatcher.matches()){ System.out.println("Invalidemailaddress."); returnfalse; } //进行其他用户注册逻辑,比如保存用户信息到数据库等 //返回注册结果 returntrue; } }
在密码重置 流程中,通常会向用户提供一个链接或验证码,并且需要发送到用户的电子邮件地址。在这种情况下,也需要验证邮箱格式合法性 :
publicclassPasswordServiceImplimplementsPasswordService{ privatestaticfinalStringEMAIL_REGEX= "^[A-Za-z0-9+_.-]+@(.+)$"; publicvoidresetPassword(PasswordInfopasswordInfo){ Patternpattern=Pattern.compile(EMAIL_REGEX); MatcheremailMatcher=pattern.matcher(passwordInfo.getEmail()); if(!emailMatcher.matches()){ System.out.println("Invalidemailaddress."); returnfalse; } //发送通知修改密码 sendReSetPasswordNotify(); } }
我们可以抽取个校验邮箱的方法 出来,又因为校验邮箱的功能在不同的类中,因此,我们可以抽个校验邮箱的工具类 :
publicclassEmailValidatorUtil{ privatestaticfinalStringEMAIL_REGEX= "^[A-Za-z0-9+_.-]+@(.+)$"; privatestaticfinalPatternpattern=Pattern.compile(EMAIL_REGEX); publicstaticbooleanisValid(Stringemail){ Matchermatcher=pattern.matcher(email); returnmatcher.matches(); } } //注册的代码可以简化为这样啦 publicclassRegisterServiceImplimplementsRegisterService{ publicbooleanregisterUser(UserInfoRequserInfo){ if(!EmailValidatorUtil.isValid(userInfo.getEmail())){ System.out.println("Invalidemailaddress."); returnfalse; } //进行其他用户注册逻辑,比如保存用户信息到数据库等 //返回注册结果 returntrue; } }
3. 反射
我们日常开发中,经常需要进行PO、DTO和VO的转化。所以大家经常看到类似的代码:
//DTO转VO publicUserInfoVOconvert(UserInfoDTOuserInfoDTO){ UserInfoVOuserInfoVO=newUserInfoVO(); userInfoVO.setUserName(userInfoDTO.getUserName()); userInfoVO.setAge(userInfoDTO.getAge()); returnuserInfoVO; } //PO转DTO publicUserInfoDTOconvert(UserInfoPOuserInfoPO){ UserInfoDTOuserInfoDTO=newUserInfoDTO(); userInfoDTO.setUserName(userInfoPO.getUserName()); userInfoDTO.setAge(userInfoPO.getAge()); returnuserInfoDTO; }
我们可以使用BeanUtils.copyProperties() 去除重复代码 ,BeanUtils.copyProperties()底层就是使用了反射 :
publicUserInfoVOconvert(UserInfoDTOuserInfoDTO){ UserInfoVOuserInfoVO=newUserInfoVO(); BeanUtils.copyProperties(userInfoDTO,userInfoVO); returnuserInfoVO; } publicUserInfoDTOconvert(UserInfoPOuserInfoPO){ UserInfoDTOuserInfoDTO=newUserInfoDTO(); BeanUtils.copyProperties(userInfoPO,userInfoDTO); returnuserInfoDTO; }
4.泛型
泛型是如何去除重复代码的呢?给大家看个例子,我有个转账明细和转账余额 对比的业务需求,有两个类似这样的方法:
privatevoidgetAndUpdateBalanceResultMap(Stringkey,Map>compareResultListMap, List balanceDTOs){ List tempList=compareResultListMap.getOrDefault(key,newArrayList<>()); tempList.addAll(balanceDTOs); compareResultListMap.put(key,tempList); } privatevoidgetAndUpdateDetailResultMap(Stringkey,Map >compareResultListMap, List detailDTOS){ List tempList=compareResultListMap.getOrDefault(key,newArrayList<>()); tempList.addAll(detailDTOS); compareResultListMap.put(key,tempList); }
这两块代码,流程功能看着很像,但是就是不能直接合并抽取一个公用方法,因为类型不一致 。单纯类型不一样的话,我们可以结合泛型 处理,因为泛型的本质就是参数化类型.优化为这样:
privatevoidgetAndUpdateResultMap(Stringkey,Map >compareResultListMap,List accountingDTOS){ List tempList=compareResultListMap.getOrDefault(key,newArrayList<>()); tempList.addAll(accountingDTOS); compareResultListMap.put(key,tempList); }
5. 继承与多态
假设你正在开发一个电子商务平台,需要处理不同类型的订单 ,例如普通订单和折扣订单。每种订单都有一些共同的属性 (如订单号、购买商品列表)和方法(如计算总价、生成订单报告),但折扣订单还有特定的属性和方法 。
在没有使用继承和多态的话,会写出类似这样的代码:
//普通订单 publicclassOrder{ privateStringorderNumber; privateListproducts; publicOrder(StringorderNumber,List products){ this.orderNumber=orderNumber; this.products=products; } publicdoublecalculateTotalPrice(){ doubletotal=0; for(Productproduct:products){ total+=product.getPrice(); } returntotal; } publicStringgenerateOrderReport(){ return"OrderReportfor"+orderNumber+":TotalPrice=$"+calculateTotalPrice(); } } //折扣订单 publicclassDiscountOrder{ privateStringorderNumber; privateList products; privatedoublediscountPercentage; publicDiscountOrder(StringorderNumber,List products,doublediscountPercentage){ this.orderNumber=orderNumber; this.products=products; this.discountPercentage=discountPercentage; } publicdoublecalculateTotalPrice(){ doubletotal=0; for(Productproduct:products){ total+=product.getPrice(); } returntotal-(total*discountPercentage/100); } publicStringgenerateOrderReport(){ return"OrderReportfor"+orderNumber+":TotalPrice=$"+calculateTotalPrice(); } }
显然,看到在Order和DiscountOrder类中,generateOrderReport() 方法的代码是完全相同的。calculateTotalPrice()则是有一点点区别,但也大相径庭。
我们可以使用继承和多态去除重复代码,让DiscountOrder去继承Order,代码如下:
publicclassOrder{ privateStringorderNumber; privateListproducts; publicOrder(StringorderNumber,List products){ this.orderNumber=orderNumber; this.products=products; } publicdoublecalculateTotalPrice(){ doubletotal=0; for(Productproduct:products){ total+=product.getPrice(); } returntotal; } publicStringgenerateOrderReport(){ return"OrderReportfor"+orderNumber+":TotalPrice=$"+calculateTotalPrice(); } } publicclassDiscountOrderextendsOrder{ privatedoublediscountPercentage; publicDiscountOrder(StringorderNumber,List products,doublediscountPercentage){ super(orderNumber,products); this.discountPercentage=discountPercentage; } @Override publicdoublecalculateTotalPrice(){ doubletotal=super.calculateTotalPrice(); returntotal-(total*discountPercentage/100); } }
6.使用设计模式
很多设计模式可以减少重复代码、提高代码的可读性、可扩展性 .比如:
工厂模式 : 通过工厂模式,你可以将对象的创建和使用分开,从而减少重复的创建代码 。
策略模式 : 策略模式定义了一族算法,将它们封装成独立的类,并使它们可以互相替换。通过使用策略模式,你可以减少在代码中重复使用相同的逻辑 。
模板方法模式 :模板方法模式定义了一个算法的骨架,将一些步骤延迟到子类中实现。这有助于避免在不同类中重复编写相似的代码 。
我给大家举个例子,模板方法是如何去除重复代码的吧 ,业务场景:
假设你正在开发一个咖啡和茶 的制作流程,制作过程中的热水和添加物质的步骤是相同的 ,但是具体的饮品制作步骤是不同的 。
如果没有使用模板方法模式,实现是酱紫的:
publicclassCoffee{ publicvoidprepareCoffee(){ boilWater(); brewCoffeeGrinds(); pourInCup(); addCondiments(); } privatevoidboilWater(){ System.out.println("Boilingwater"); } privatevoidbrewCoffeeGrinds(){ System.out.println("Brewingcoffeegrinds"); } privatevoidpourInCup(){ System.out.println("Pouringintocup"); } privatevoidaddCondiments(){ System.out.println("Addingsugarandmilk"); } } publicclassTea{ publicvoidprepareTea(){ boilWater(); steepTeaBag(); pourInCup(); addLemon(); } privatevoidboilWater(){ System.out.println("Boilingwater"); } privatevoidsteepTeaBag(){ System.out.println("Steepingtheteabag"); } privatevoidpourInCup(){ System.out.println("Pouringintocup"); } privatevoidaddLemon(){ System.out.println("Addinglemon"); } }
这个代码例子,我们可以发现,烧水和倒入杯子的步骤代码 ,在Coffee和Tea类中是重复的。
使用模板方法模式,代码可以优化成这样:
abstractclassBeverage{ publicfinalvoidprepareBeverage(){ boilWater(); brew(); pourInCup(); addCondiments(); } privatevoidboilWater(){ System.out.println("Boilingwater"); } abstractvoidbrew(); privatevoidpourInCup(){ System.out.println("Pouringintocup"); } abstractvoidaddCondiments(); } classCoffeeextendsBeverage{ @Override voidbrew(){ System.out.println("Brewingcoffeegrinds"); } @Override voidaddCondiments(){ System.out.println("Addingsugarandmilk"); } } classTeaextendsBeverage{ @Override voidbrew(){ System.out.println("Steepingtheteabag"); } @Override voidaddCondiments(){ System.out.println("Addinglemon"); } }
在这个例子中,我们创建了一个抽象类Beverage,其中定义了制作饮品的模板方法 prepareBeverage()。这个方法包含了烧水、倒入杯子 等共同的步骤,而将制作过程中的特定步骤 brew() 和 addCondiments() 延迟到子类中实现。这样,我们避免了在每个具体的饮品类中重复编写相同的烧水和倒入杯子的代码 ,提高了代码的可维护性和重用性。
7.自定义注解(或者说AOP面向切面)
使用 AOP框架可以在不同地方插入通用的逻辑,从而减少代码重复。
业务场景:
假设你正在开发一个Web应用程序,需要对不同的Controller方法进行权限检查。每个Controller方法都需要进行类似的权限验证,但是重复的代码会导致代码的冗余和维护困难 。
publicclassMyController{ publicvoidviewData(){ if(!User.hasPermission("read")){ thrownewSecurityException("Insufficientpermissiontoaccessthisresource."); } //Methodimplementation } publicvoidmodifyData(){ if(!User.hasPermission("write")){ thrownewSecurityException("Insufficientpermissiontoaccessthisresource."); } //Methodimplementation } }
你可以看到在每个需要权限校验的方法中都需要重复编写相同的权限校验逻辑 ,即出现了重复代码 .我们使用自定义注解的方式 能够将权限校验逻辑集中管理,通过切面来处理,消除重复代码 .如下:
@Aspect @Component publicclassPermissionAspect{ @Before("@annotation(requiresPermission)") publicvoidcheckPermission(RequiresPermissionrequiresPermission){ Stringpermission=requiresPermission.value(); if(!User.hasPermission(permission)){ thrownewSecurityException("Insufficientpermissiontoaccessthisresource."); } } } publicclassMyController{ @RequiresPermission("read") publicvoidviewData(){ //Methodimplementation } @RequiresPermission("write") publicvoidmodifyData(){ //Methodimplementation } }
就这样,不管多少个Controller方法需要进行权限检查,你只需在方法上添加相应的注解即可 。权限检查的逻辑在切面中集中管理,避免了在每个Controller方法中重复编写相同的权限验证代码。这大大提高了代码的可读性、可维护性,并避免了代码冗余。
8.函数式接口和Lambda表达式
业务场景:
假设你正在开发一个应用程序,需要根据不同的条件来过滤一组数据 。每次过滤的逻辑都可能会有些微的不同,但基本的流程是相似的。
没有使用函数式接口和Lambda表达式的情况:
publicclassDataFilter{ publicListfilterPositiveNumbers(List numbers){ List result=newArrayList<>(); for(Integernumber:numbers){ if(number>0){ result.add(number); } } returnresult; } publicList filterEvenNumbers(List numbers){ List result=newArrayList<>(); for(Integernumber:numbers){ if(number%2==0){ result.add(number); } } returnresult; } }
在这个例子中,我们有两个不同的方法来过滤一组数据,但是基本的循环和条件判断逻辑是重复的,我们可以使用使用函数式接口和Lambda表达式,去除重复代码,如下:
publicclassDataFilter{ publicListfilterNumbers(List numbers,Predicate predicate){ List result=newArrayList<>(); for(Integernumber:numbers){ if(predicate.test(number)){ result.add(number); } } returnresult; } }
我们将过滤的核心逻辑抽象出来。该方法接受一个 Predicate函数式接口作为参数,以便根据不同的条件来过滤数据。然后,我们可以使用Lambda表达式来传递具体的条件,这样最终也达到去除重复代码的效果啦.
审核编辑:汤梓红
-
冗余
+关注
关注
1文章
110浏览量
20209 -
函数
+关注
关注
3文章
4326浏览量
62558 -
代码
+关注
关注
30文章
4776浏览量
68508 -
spring
+关注
关注
0文章
340浏览量
14334
原文标题:优化重复冗余代码的8种方式!
文章出处:【微信号:芋道源码,微信公众号:芋道源码】欢迎添加关注!文章转载请注明出处。
发布评论请先 登录
相关推荐
评论