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