來源:撿田螺的小男孩
最近工作中,我通過層層優(yōu)化重復(fù)代碼 ,最后抽出個通用模板.因此跟大家分享一下優(yōu)化以及思考的過程。我會先造一個相似的例子,然后一步步帶大家如何優(yōu)化哈 ,看完一定會有幫助的。
優(yōu)化前的例子
第一步優(yōu)化:抽取公用方法
第二步優(yōu)化:反射對比字段
第三步優(yōu)化:泛型+ lambda 函數(shù)式
第四步優(yōu)化:繼承多態(tài)
第五步優(yōu)化:模板方法成型
大功告成: 策略模式+工廠模式+模板方法模式
1. 優(yōu)化前的例子
在這里,我先給大家模擬一個業(yè)務(wù)場景哈,并給出些簡化版的代碼
假設(shè)你有個對賬需求:你要把文件服務(wù)器中,兩個A、B 不同端,上送的余額明細和轉(zhuǎn)賬明細 ,下載下來,對比每個字段是否一致 .
明細和余額的對比類似 ,代碼整體流程:
讀取A、B端文件到內(nèi)存的兩個list
兩個list通過某個唯一key轉(zhuǎn)化為map
兩個map字段逐個對比
我們先看明細對比 哈,可以寫出類似醬紫的代碼:
?
//對比明細 private?void?checkDetail(String?detailPathOfA,String?detailPathOfB?)throws?IOException{ ???//讀取A端的文件 ???List?resultListOfA?=?new?ArrayList<>(); ???try?(BufferedReader?reader1?=?new?BufferedReader(new?FileReader(detailPathOfA)))?{ ????????????String?line; ????????????while?((line?=?reader1.readLine())?!=?null)?{ ????????????????resultListOfA.add(DetailDTO.convert(line)); ????????????} ????????} ???//讀取B端的文件 ???List ?resultListOfB?=?new?ArrayList<>(); ???try?(BufferedReader?reader1?=?new?BufferedReader(new?FileReader(detailPathOfB)))?{ ????????????String?line; ????????????while?((line?=?reader1.readLine())?!=?null)?{ ????????????????resultListOfB.add(DetailDTO.convert(line)); ????????????} ????????} ????//A列表轉(zhuǎn)化為Map ????Map ?resultMapOfA?=?new?HashMap<>(); ????for(DetailDTO?detail:resultListOfA){ ????????resultMapOfA.put(detail.getBizSeq(),detail); ????} ?????//B列表轉(zhuǎn)化為Map ????Map ?resultMapOfB?=?new?HashMap<>() ????for(DetailDTO?detail:resultListOfB){ ????????resultMapOfB.put(detail.getBizSeq(),detail); ????} ????//明細逐個對比 ????for?(Map.Entry ?temp?:?resultMapOfA.entrySet())?{ ????????if?(resultMapOfB.containsKey(temp.getKey()))?{ ????????????DetailDTO?detailOfA?=?temp.getValue(); ????????????DetailDTO?detailOfB?=?resultMapOfB.get(temp.getKey()); ????????????if?(!detailOfA.getAmt().equals(detailOfB.getAmt()))?{ ??????????????????log.warn("amt?is?different,key:{}",?temp.getKey()); ????????????} ????????????if?(!detailOfA.getDate().equals(detailOfB.getDate()))?{ ????????????????log.warn("date?is?different,key:{}",?temp.getKey()); ????????????} ????????????if?(!detailOfA.getStatus().equals(detailOfB.getStatus()))?{ ????????????????log.warn("status?is?different,key:{}",?temp.getKey()); ????????????} ????????????...... ????????} ??} }
?
基于 Spring Boot + MyBatis Plus + Vue & Element 實現(xiàn)的后臺管理系統(tǒng) + 用戶小程序,支持 RBAC 動態(tài)權(quán)限、多租戶、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能
2. 抽取公用方法去重
大家仔細看 以上明細對比的例子 ,發(fā)現(xiàn)了重復(fù) 代碼:
我們可以抽取一個公用方法去優(yōu)化它 ,比如抽取個讀取文件的公用方法 readFile:
同理,這塊代碼也是重復(fù) 了:
我們也可以抽個公用方法: convertListToMap
通過抽取公用方法后,已經(jīng)優(yōu)雅很多啦~
基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實現(xiàn)的后臺管理系統(tǒng) + 用戶小程序,支持 RBAC 動態(tài)權(quán)限、多租戶、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能
3. 反射對比字段
我們再來看下字段對比的邏輯,如下:
以上代碼會取兩個對象的每個字段對比 ,如果明細對象的屬性字段特別多的話 ,這塊代碼也會顯得重復(fù)冗余 。我們可以通過反射去對比兩個對象的屬性,如下:
有了這個反射對比方法 ,原來的代碼就可以優(yōu)化成這樣啦,是不是優(yōu)雅了很多:
4.Lambda 函數(shù)式+泛型
實現(xiàn)完明細文件的對比,我們還需要余額文件的對比 :
同樣的,也是先讀取文件 ,如下:
大家可以發(fā)現(xiàn),讀取余額文件和剛剛的讀取明細文件 很像,有一部分代碼是重復(fù)的 ,但是不能直接一下子抽個共同函數(shù) 出來:
對了,convert方法是醬紫的哈 :
大家可以發(fā)現(xiàn),就是一個返回類型,以及這個對應(yīng)類型的一個靜態(tài) convert 方法不一致而已 ,如果是類型不一樣,我們可以使用泛型替代 ,如果是一個小的靜態(tài)方法不一致,我們則可以使用lambda函數(shù)式接口提取,因此可以抽這個這么一個公用方法吧:
平時我們用泛型+ Lambda 表達式結(jié)合,去抽取公用方法 ,代碼就顯得高端大氣很多,對吧~
5. 繼承多態(tài).
在余額對比文件中,讀取完文件到內(nèi)存后,我們需要把通過某個唯一key關(guān)聯(lián)起來,即把List轉(zhuǎn)為Map,如下:
一般來說,把兩個list轉(zhuǎn)化為Map,抽一個公用方法是不是就好了?比如說醬紫:
其實也行,但是其實可以更抽象一點 。因為余額和明細對比 都有l(wèi)ist轉(zhuǎn)map的需求,而且也是有共性的,只不過是轉(zhuǎn)化map的key和value的類型不一致而已
圖片
我們仔細思考一下,value類型是不同類型(分別是BalanceDTO 和 DetailDTO ),而key則是對應(yīng)對象的一個或者某幾個屬性連接起來的 。對于不同類型,我們可以考慮泛型。對于余額和明細對象不同的key的話,我們則可以考慮繼承和多態(tài),讓它們實現(xiàn)同一個接口就好啦。
我們可以使用繼承和多態(tài),定義一個抽象類BaseKeyDTO,里面有個getKey的抽象方法,然后BalanceDTO 和DetailDTO都繼承它,實現(xiàn)各自getKey的方法,如下:
最后,我們應(yīng)用繼承多態(tài)+擴展泛型(
最后明細和余額 對比,可以優(yōu)化成這樣,其實看起來已經(jīng)比較優(yōu)雅啦 :
?
???//對比明細 ????private?void?checkDetail(String?detailPathOfA,?String?detailPathOfB)?throws?IOException?{ ????????//讀取A端明細的文件 ????????List?resultListOfA?=?readDataFromFile(detailPathOfA,?DetailDTO::convert); ????????//讀取B端明細的文件 ????????List ?resultListOfB?=?readDataFromFile(detailPathOfB,?DetailDTO::convert); ????????//A列表轉(zhuǎn)化為Map ????????Map ?resultMapOfA?=?convertListToMap(resultListOfA); ????????//B列表轉(zhuǎn)化為Map ????????Map ?resultMapOfB?=?convertListToMap(resultListOfB); ????????//明細逐個對比 ????????compareDifferent(resultMapOfA,resultMapOfB); ????} ???//對比余額 ????private?void?checkBalance(String?balancePathOfA,String?detailPathOfB)?throws?IOException?{ ????????//讀取A端余額的文件 ????????List ?resultListOfA?=?readDataFromFile(balancePathOfA,BalanceDTO::convert); ????????//讀取B端余額的文件 ????????List ?resultListOfB?=?readDataFromFile(detailPathOfB,BalanceDTO::convert); ????????//A余額列表轉(zhuǎn)化為Map ????????Map ?resultMapOfA?=?convertListToMap(resultListOfA); ????????//B余額列表轉(zhuǎn)化為Map ????????Map ?resultMapOfB?=?convertListToMap(resultListOfB); ????????//余額逐個對比 ????????compareDifferent(resultMapOfA,resultMapOfB); ????} ????//對比也用泛型,抽一個公用的方法哈 ????private?void?compareDifferent(Map ?mapA,?Map ?mapB)?{ ????????for?(Map.Entry ?temp?:?mapA.entrySet())?{ ????????????if?(mapB.containsKey(temp.getKey()))?{ ????????????????T?dtoA?=?temp.getValue(); ????????????????T?dtoB?=?mapB.get(temp.getKey()); ????????????????List ?resultList?=?compareObjects(dtoA,?dtoB); ????????????????for?(String?tempStr?:?resultList)?{ ????????????????????log.warn("{}?is?different,key:{}",?tempStr,?dtoA.getKey()); ????????????????} ????????????} ????????} ????} }
?
6. 模板方法
大家回頭細看,可以發(fā)現(xiàn)不管是明細還是余額 對比,兩個方法很像,都是一個骨架流程 來的:
讀取A、B端文件到內(nèi)存的兩個list
兩個list通過某個唯一key轉(zhuǎn)化為map
兩個map字段逐個對比
大家先回想一下模板方法模式 :
定義了一個算法的骨架 ,將一些步驟延遲到子類中實現(xiàn)。這有助于避免在不同類中重復(fù)編寫相似的代碼。
頓時是不是就覺得這塊代碼還有優(yōu)化空間~~
6.1 定義對比模板的骨架
我們可以嘗試這兩塊代碼再合并,用模板方法優(yōu)化它。我們先定義一個模板,然后模板內(nèi)定義它們骨架的流程 ,如下:
6.2 模板的方法逐步細化
因為readDataFromFile需要輸出兩個list,所以我們可以定義返回類型為Pair,代碼如下:
又因為這個函數(shù)式的轉(zhuǎn)化,是不同子類才能定下來的 ,我們就可以聲明個抽象方法convertLineToDTD,讓子類去實現(xiàn)。因此模板就變成這樣啦:
同理,還有兩個list轉(zhuǎn)化為兩個map再對比,我們可以聲明為這樣:
因此最終模板就是這樣啦 :
6.3 不同對比子類
如果你是余額對比,那你聲明一個CheckBalanceStrategyServiceImpl去繼承抽象模板
如果你是明細對比 ,那你聲明一個CheckDetailStrategyServiceImpl去繼承抽象模板
這兩個不同的子類,就像不同的策略,我們應(yīng)該都能嗅到策略模式 的味道啦~
7. 工廠模式+ 模板方法 + 策略模式全家桶
有了明細對比、余額對比的模板,為了更方便調(diào)用,我們還可以定義一個校驗策略 接口,然后交給spring工廠 類,這樣更方便調(diào)用。其實日常開發(fā)中,這三種設(shè)計模式一般一起出現(xiàn),非常實用 :
我們先聲明一個校驗ICheckStrategy接口:
然后模板AbstractCheckTemplate實現(xiàn)ICheckStrategy接口:
接著,不同對比策略類CheckDetailStrategyServiceImpl 和CheckDetailStrategyServiceImpl映射對應(yīng)的對比校驗類型:
?
/** ?*?明細對比策略 ?*/ @Service public?class?CheckDetailStrategyServiceImpl?extends?AbstractCheckTemplate?{ ?????@Override ????protected?DetailDTO?convertLineToDTD(String?line)?{ ????????return?DetailDTO.convert(line); ????} ????@Override ????public?void?check(String?filePathA,?String?filePathB)?throws?IOException?{ ????????checkTemplate(filePathA,?filePathB); ????} ????//對比校驗類型為:明細 ????@Override ????public?CheckEnum?getCheckEnum()?{ ????????return?CheckEnum.DETAIL_CHECK; ????} } /** ?*?余額對比策略 ?*/ @Service public?class?CheckBalanceStrategyServiceImpl?extends?AbstractCheckTemplate ?{ ????@Override ????public?void?check(String?filePathA,?String?filePathB)?throws?IOException?{ ????????checkTemplate(filePathA,?filePathB); ????} ?????//對比校驗類型為:余額 ????@Override ????public?CheckEnum?getCheckEnum()?{ ????????return?CheckEnum.BALANCE_CHECK; ????} ????@Override ????protected?BalanceDTO?convertLineToDTD(String?line)?{ ????????return?BalanceDTO.convert(line); ????} }
?
最后一步,我們借助spring的生命周期,使用ApplicationContextAware接口,把對用的策略,初始化到map里面。然后對外提供checkCompare方法即可。讓調(diào)用者決定用哪一種對比,其實這算工廠模式思想 ,大家可以自己思考一下~
最后
本文介紹了:如何將一些通用的、用于優(yōu)化重復(fù)冗余代碼的技巧應(yīng)用到開發(fā)中。最終,我通過這些技巧將代碼優(yōu)化成一個通用模板。很有實踐的意義~
審核編輯:湯梓紅
評論
查看更多