來源:撿田螺的小男孩
前言
日常開發(fā)中,我們經(jīng)常會遇到一些重復(fù)冗余的代碼 。大家都知道重復(fù)代碼不好 ,它主要有這些缺點:可維護性差、可讀性差、增加錯誤風(fēng)險 等等。最近呢,我優(yōu)化了一些系統(tǒng)中的重復(fù)代碼,用了好幾種的方式,感覺挺有用的。所以本文給大家講講優(yōu)化重復(fù)冗余代碼的幾種方式~
抽取公用方法
抽個工具類
反射
泛型
繼承和多態(tài)
設(shè)計模式
函數(shù)式Lambda
AOP切面
基于 Spring Boot + MyBatis Plus + Vue & Element 實現(xiàn)的后臺管理系統(tǒng) + 用戶小程序,支持 RBAC 動態(tài)權(quán)限、多租戶、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能
1. 抽取公用方法
抽取公用方法 ,是最常用的代碼去重方式 ~
比如這個例子,分別遍歷names列表,然后各自轉(zhuǎn)化為大寫和小寫打印出來:
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過程,代碼是重復(fù)冗余的,只不過轉(zhuǎn)化大小寫不一樣而已 。我們可以抽個公用方法 processNames,優(yōu)化成這樣:
publicclassTianLuoExample{
publicstaticvoidprocessNames(Listnames,FunctionnameProcessor,StringprocessType){
System.out.println(processType+"Names:");
for(Stringname:names){
StringprocessedName=nameProcessor.apply(name);
System.out.println(processedName);
}
}
publicstaticvoidmain(String[]args){
Listnames=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 實現(xiàn)的后臺管理系統(tǒng) + 用戶小程序,支持 RBAC 動態(tài)權(quán)限、多租戶、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能
2. 抽工具類
我們優(yōu)化重復(fù)代碼,抽一個公用方法后,如果發(fā)現(xiàn)這個方法有更多共性,就可以把公用方法升級為一個工具類 。比如這樣的業(yè)務(wù)場景:注冊,修改郵箱,重置密碼等,都需要校驗郵箱
實現(xiàn)注冊功能時,用戶會填郵箱,需要驗證郵箱格式 ,
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; } //進行其他用戶注冊邏輯,比如保存用戶信息到數(shù)據(jù)庫等 //返回注冊結(jié)果 returntrue; } }
在密碼重置 流程中,通常會向用戶提供一個鏈接或驗證碼,并且需要發(fā)送到用戶的電子郵件地址。在這種情況下,也需要驗證郵箱格式合法性 :
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;
}
//發(fā)送通知修改密碼
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;
}
//進行其他用戶注冊邏輯,比如保存用戶信息到數(shù)據(jù)庫等
//返回注冊結(jié)果
returntrue;
}
}
3. 反射
我們?nèi)粘i_發(fā)中,經(jīng)常需要進行PO、DTO和VO的轉(zhuǎn)化。所以大家經(jīng)常看到類似的代碼:
//DTO轉(zhuǎn)VO
publicUserInfoVOconvert(UserInfoDTOuserInfoDTO){
UserInfoVOuserInfoVO=newUserInfoVO();
userInfoVO.setUserName(userInfoDTO.getUserName());
userInfoVO.setAge(userInfoDTO.getAge());
returnuserInfoVO;
}
//PO轉(zhuǎn)DTO
publicUserInfoDTOconvert(UserInfoPOuserInfoPO){
UserInfoDTOuserInfoDTO=newUserInfoDTO();
userInfoDTO.setUserName(userInfoPO.getUserName());
userInfoDTO.setAge(userInfoPO.getAge());
returnuserInfoDTO;
}
我們可以使用BeanUtils.copyProperties() 去除重復(fù)代碼 ,BeanUtils.copyProperties()底層就是使用了反射 :
publicUserInfoVOconvert(UserInfoDTOuserInfoDTO){
UserInfoVOuserInfoVO=newUserInfoVO();
BeanUtils.copyProperties(userInfoDTO,userInfoVO);
returnuserInfoVO;
}
publicUserInfoDTOconvert(UserInfoPOuserInfoPO){
UserInfoDTOuserInfoDTO=newUserInfoDTO();
BeanUtils.copyProperties(userInfoPO,userInfoDTO);
returnuserInfoDTO;
}
4.泛型
泛型是如何去除重復(fù)代碼的呢?給大家看個例子,我有個轉(zhuǎn)賬明細和轉(zhuǎn)賬余額 對比的業(yè)務(wù)需求,有兩個類似這樣的方法:
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); }
這兩塊代碼,流程功能看著很像,但是就是不能直接合并抽取一個公用方法,因為類型不一致 。單純類型不一樣的話,我們可以結(jié)合泛型 處理,因為泛型的本質(zhì)就是參數(shù)化類型.優(yōu)化為這樣:
privatevoidgetAndUpdateResultMap(Stringkey,Map >compareResultListMap,List accountingDTOS){ List tempList=compareResultListMap.getOrDefault(key,newArrayList<>()); tempList.addAll(accountingDTOS); compareResultListMap.put(key,tempList); }
5. 繼承與多態(tài)
假設(shè)你正在開發(fā)一個電子商務(wù)平臺,需要處理不同類型的訂單 ,例如普通訂單和折扣訂單。每種訂單都有一些共同的屬性 (如訂單號、購買商品列表)和方法(如計算總價、生成訂單報告),但折扣訂單還有特定的屬性和方法 。
在沒有使用繼承和多態(tài)的話,會寫出類似這樣的代碼:
//普通訂單
publicclassOrder{
privateStringorderNumber;
privateListproducts;
publicOrder(StringorderNumber,Listproducts){
this.orderNumber=orderNumber;
this.products=products;
}
publicdoublecalculateTotalPrice(){
doubletotal=0;
for(Productproduct:products){
total+=product.getPrice();
}
returntotal;
}
publicStringgenerateOrderReport(){
return"OrderReportfor"+orderNumber+":TotalPrice=$"+calculateTotalPrice();
}
}
//折扣訂單
publicclassDiscountOrder{
privateStringorderNumber;
privateListproducts;
privatedoublediscountPercentage;
publicDiscountOrder(StringorderNumber,Listproducts,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()則是有一點點區(qū)別,但也大相徑庭。
我們可以使用繼承和多態(tài)去除重復(fù)代碼,讓DiscountOrder去繼承Order,代碼如下:
publicclassOrder{
privateStringorderNumber;
privateListproducts;
publicOrder(StringorderNumber,Listproducts){
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,Listproducts,doublediscountPercentage){
super(orderNumber,products);
this.discountPercentage=discountPercentage;
}
@Override
publicdoublecalculateTotalPrice(){
doubletotal=super.calculateTotalPrice();
returntotal-(total*discountPercentage/100);
}
}
6.使用設(shè)計模式
很多設(shè)計模式可以減少重復(fù)代碼、提高代碼的可讀性、可擴展性 .比如:
工廠模式 : 通過工廠模式,你可以將對象的創(chuàng)建和使用分開,從而減少重復(fù)的創(chuàng)建代碼 。
策略模式 : 策略模式定義了一族算法,將它們封裝成獨立的類,并使它們可以互相替換。通過使用策略模式,你可以減少在代碼中重復(fù)使用相同的邏輯 。
模板方法模式 :模板方法模式定義了一個算法的骨架,將一些步驟延遲到子類中實現(xiàn)。這有助于避免在不同類中重復(fù)編寫相似的代碼 。
我給大家舉個例子,模板方法是如何去除重復(fù)代碼的吧 ,業(yè)務(wù)場景:
假設(shè)你正在開發(fā)一個咖啡和茶 的制作流程,制作過程中的熱水和添加物質(zhì)的步驟是相同的 ,但是具體的飲品制作步驟是不同的 。
如果沒有使用模板方法模式,實現(xiàn)是醬紫的:
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");
}
}
這個代碼例子,我們可以發(fā)現(xiàn),燒水和倒入杯子的步驟代碼 ,在Coffee和Tea類中是重復(fù)的。
使用模板方法模式,代碼可以優(yōu)化成這樣:
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");
}
}
在這個例子中,我們創(chuàng)建了一個抽象類Beverage,其中定義了制作飲品的模板方法 prepareBeverage()。這個方法包含了燒水、倒入杯子 等共同的步驟,而將制作過程中的特定步驟 brew() 和 addCondiments() 延遲到子類中實現(xiàn)。這樣,我們避免了在每個具體的飲品類中重復(fù)編寫相同的燒水和倒入杯子的代碼 ,提高了代碼的可維護性和重用性。
7.自定義注解(或者說AOP面向切面)
使用 AOP框架可以在不同地方插入通用的邏輯,從而減少代碼重復(fù)。
業(yè)務(wù)場景:
假設(shè)你正在開發(fā)一個Web應(yīng)用程序,需要對不同的Controller方法進行權(quán)限檢查。每個Controller方法都需要進行類似的權(quán)限驗證,但是重復(fù)的代碼會導(dǎo)致代碼的冗余和維護困難 。
publicclassMyController{
publicvoidviewData(){
if(!User.hasPermission("read")){
thrownewSecurityException("Insufficientpermissiontoaccessthisresource.");
}
//Methodimplementation
}
publicvoidmodifyData(){
if(!User.hasPermission("write")){
thrownewSecurityException("Insufficientpermissiontoaccessthisresource.");
}
//Methodimplementation
}
}
你可以看到在每個需要權(quán)限校驗的方法中都需要重復(fù)編寫相同的權(quán)限校驗邏輯 ,即出現(xiàn)了重復(fù)代碼 .我們使用自定義注解的方式 能夠?qū)?quán)限校驗邏輯集中管理,通過切面來處理,消除重復(fù)代碼 .如下:
@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方法需要進行權(quán)限檢查,你只需在方法上添加相應(yīng)的注解即可 。權(quán)限檢查的邏輯在切面中集中管理,避免了在每個Controller方法中重復(fù)編寫相同的權(quán)限驗證代碼。這大大提高了代碼的可讀性、可維護性,并避免了代碼冗余。
8.函數(shù)式接口和Lambda表達式
業(yè)務(wù)場景:
假設(shè)你正在開發(fā)一個應(yīng)用程序,需要根據(jù)不同的條件來過濾一組數(shù)據(jù) 。每次過濾的邏輯都可能會有些微的不同,但基本的流程是相似的。
沒有使用函數(shù)式接口和Lambda表達式的情況:
publicclassDataFilter{
publicListfilterPositiveNumbers(Listnumbers){
Listresult=newArrayList<>();
for(Integernumber:numbers){
if(number>0){
result.add(number);
}
}
returnresult;
}
publicListfilterEvenNumbers(Listnumbers){
Listresult=newArrayList<>();
for(Integernumber:numbers){
if(number%2==0){
result.add(number);
}
}
returnresult;
}
}
在這個例子中,我們有兩個不同的方法來過濾一組數(shù)據(jù),但是基本的循環(huán)和條件判斷邏輯是重復(fù)的,我們可以使用使用函數(shù)式接口和Lambda表達式,去除重復(fù)代碼,如下:
publicclassDataFilter{
publicListfilterNumbers(Listnumbers,Predicatepredicate){
Listresult=newArrayList<>();
for(Integernumber:numbers){
if(predicate.test(number)){
result.add(number);
}
}
returnresult;
}
}
我們將過濾的核心邏輯抽象出來。該方法接受一個 Predicate函數(shù)式接口作為參數(shù),以便根據(jù)不同的條件來過濾數(shù)據(jù)。然后,我們可以使用Lambda表達式來傳遞具體的條件,這樣最終也達到去除重復(fù)代碼的效果啦.
審核編輯:湯梓紅
-
冗余
+關(guān)注
關(guān)注
1文章
113瀏覽量
21253 -
函數(shù)
+關(guān)注
關(guān)注
3文章
4417瀏覽量
67504 -
代碼
+關(guān)注
關(guān)注
30文章
4968瀏覽量
73960 -
spring
+關(guān)注
關(guān)注
0文章
341瀏覽量
15935
原文標(biāo)題:優(yōu)化重復(fù)冗余代碼的8種方式!
文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
嵌入式代碼優(yōu)化技巧
HarmonyOS優(yōu)化應(yīng)用包體積大小問題性能優(yōu)化
一種冗余熱備份電源的設(shè)計
UPS系統(tǒng)并機冗余運行方式的選擇
分享兩種MOS冗余驅(qū)動方案
電源系統(tǒng)冗余性研究
一種語義規(guī)則為指導(dǎo)的增量優(yōu)化方法
支持實時替換的混合冗余策略優(yōu)化
怎么優(yōu)化冗余基帶板調(diào)配?資料下載
自學(xué)單片機編程(四)流水燈代碼優(yōu)化
優(yōu)化重復(fù)冗余代碼的8種方式
評論