代碼精簡10倍,責(zé)任鏈模式y(tǒng)yds
目錄
- 背景
- 什么是責(zé)任鏈
- 使用場景
- 結(jié)語
背景
最近,我讓團(tuán)隊(duì)內(nèi)一位成員寫了一個(gè)導(dǎo)入功能。他使用了責(zé)任鏈模式,代碼堆的非常多,bug 也多,沒有達(dá)到我預(yù)期的效果。
實(shí)際上,針對導(dǎo)入功能,我認(rèn)為模版方法更合適!為此,隔壁團(tuán)隊(duì)也拿出我們的案例,進(jìn)行了集體 code review。
學(xué)好設(shè)計(jì)模式,且不要為了練習(xí),強(qiáng)行使用!讓原本 100 行就能實(shí)現(xiàn)的功能,寫了 3000 行!對錯(cuò)暫且不論,我們先一起看看責(zé)任鏈設(shè)計(jì)模式吧!
什么是責(zé)任鏈
責(zé)任鏈模式是一種行為設(shè)計(jì)模式, 允許你將請求沿著處理者鏈進(jìn)行發(fā)送。收到請求后, 每個(gè)處理者均可對請求進(jìn)行處理, 或?qū)⑵鋫鬟f給鏈上的下個(gè)處理者。
圖片使用場景
責(zé)任鏈的使用場景還是比較多的:
-
多條件流程判斷:權(quán)限控制
-
ERP 系統(tǒng)流程審批:總經(jīng)理、人事經(jīng)理、項(xiàng)目經(jīng)理
-
Java 過濾器的底層實(shí)現(xiàn) Filter
如果不使用該設(shè)計(jì)模式,那么當(dāng)需求有所改變時(shí),就會(huì)使得代碼臃腫或者難以維護(hù),例如下面的例子。
| 反例
假設(shè)現(xiàn)在有一個(gè)闖關(guān)游戲,進(jìn)入下一關(guān)的條件是上一關(guān)的分?jǐn)?shù)要高于 xx:
-
游戲一共 3 個(gè)關(guān)卡
-
進(jìn)入第二關(guān)需要第一關(guān)的游戲得分大于等于 80
-
進(jìn)入第三關(guān)需要第二關(guān)的游戲得分大于等于 90
那么代碼可以這樣寫:
//第一關(guān)??
public?class?FirstPassHandler?{??
????public?int?handler(){??
????????System.out.println("第一關(guān)-->FirstPassHandler");??
????????return?80;??
????}??
}??
??
//第二關(guān)??
public?class?SecondPassHandler?{??
????public?int?handler(){??
????????System.out.println("第二關(guān)-->SecondPassHandler");??
????????return?90;??
????}??
}??
??
??
//第三關(guān)??
public?class?ThirdPassHandler?{??
????public?int?handler(){??
????????System.out.println("第三關(guān)-->ThirdPassHandler,這是最后一關(guān)啦");??
????????return?95;??
????}??
}??
??
??
//客戶端??
public?class?HandlerClient?{??
????public?static?void?main(String[]?args)?{??
??
????????FirstPassHandler?firstPassHandler?=?new?FirstPassHandler();//第一關(guān)??
????????SecondPassHandler?secondPassHandler?=?new?SecondPassHandler();//第二關(guān)??
????????ThirdPassHandler?thirdPassHandler?=?new?ThirdPassHandler();//第三關(guān)??
??
????????int?firstScore?=?firstPassHandler.handler();??
????????//第一關(guān)的分?jǐn)?shù)大于等于80則進(jìn)入第二關(guān)??
????????if(firstScore?>=?80){??
????????????int?secondScore?=?secondPassHandler.handler();??
????????????//第二關(guān)的分?jǐn)?shù)大于等于90則進(jìn)入第二關(guān)??
????????????if(secondScore?>=?90){??
????????????????thirdPassHandler.handler();??
????????????}??
????????}??
????}??
}??
那么如果這個(gè)游戲有 100 關(guān),我們的代碼很可能就會(huì)寫成這個(gè)樣子:
if(第1關(guān)通過){??
????//?第2關(guān)?游戲??
????if(第2關(guān)通過){??
????????//?第3關(guān)?游戲??
????????if(第3關(guān)通過){??
???????????//?第4關(guān)?游戲??
????????????if(第4關(guān)通過){??
????????????????//?第5關(guān)?游戲??
????????????????if(第5關(guān)通過){??
????????????????????//?第6關(guān)?游戲??
????????????????????if(第6關(guān)通過){??
????????????????????????//...??
????????????????????}??
????????????????}??
????????????}???
????????}??
????}??
}??
這種代碼不僅冗余,并且當(dāng)我們要將某兩關(guān)進(jìn)行調(diào)整時(shí)會(huì)對代碼非常大的改動(dòng),這種操作的風(fēng)險(xiǎn)是很高的,因此,該寫法非常糟糕。
| 初步改造
如何解決這個(gè)問題,我們可以通過鏈表將每一關(guān)連接起來,形成責(zé)任鏈的方式,第一關(guān)通過后是第二關(guān),第二關(guān)通過后是第三關(guān)....
這樣客戶端就不需要進(jìn)行多重 if 的判斷了:
public?class?FirstPassHandler?{??
????/**??
?????*?第一關(guān)的下一關(guān)是?第二關(guān)??
?????*/??
????private?SecondPassHandler?secondPassHandler;??
??
????public?void?setSecondPassHandler(SecondPassHandler?secondPassHandler)?{??
????????this.secondPassHandler?=?secondPassHandler;??
????}??
??
????//本關(guān)卡游戲得分??
????private?int?play(){??
????????return?80;??
????}??
??
????public?int?handler(){??
????????System.out.println("第一關(guān)-->FirstPassHandler");??
????????if(play()?>=?80){??
????????????//分?jǐn)?shù)>=80?并且存在下一關(guān)才進(jìn)入下一關(guān)??
????????????if(this.secondPassHandler?!=?null){??
????????????????return?this.secondPassHandler.handler();??
????????????}??
????????}??
??
????????return?80;??
????}??
}??
??
public?class?SecondPassHandler?{??
??
????/**??
?????*?第二關(guān)的下一關(guān)是?第三關(guān)??
?????*/??
????private?ThirdPassHandler?thirdPassHandler;??
??
????public?void?setThirdPassHandler(ThirdPassHandler?thirdPassHandler)?{??
????????this.thirdPassHandler?=?thirdPassHandler;??
????}??
??
????//本關(guān)卡游戲得分??
????private?int?play(){??
????????return?90;??
????}??
??
????public?int?handler(){??
????????System.out.println("第二關(guān)-->SecondPassHandler");??
??
????????if(play()?>=?90){??
????????????//分?jǐn)?shù)>=90?并且存在下一關(guān)才進(jìn)入下一關(guān)??
????????????if(this.thirdPassHandler?!=?null){??
????????????????return?this.thirdPassHandler.handler();??
????????????}??
????????}??
??
????????return?90;??
????}??
}??
??
public?class?ThirdPassHandler?{??
??
????//本關(guān)卡游戲得分??
????private?int?play(){??
????????return?95;??
????}??
??
????/**??
?????*?這是最后一關(guān),因此沒有下一關(guān)??
?????*/??
????public?int?handler(){??
????????System.out.println("第三關(guān)-->ThirdPassHandler,這是最后一關(guān)啦");??
????????return?play();??
????}??
}??
??
public?class?HandlerClient?{??
????public?static?void?main(String[]?args)?{??
??
????????FirstPassHandler?firstPassHandler?=?new?FirstPassHandler();//第一關(guān)??
????????SecondPassHandler?secondPassHandler?=?new?SecondPassHandler();//第二關(guān)??
????????ThirdPassHandler?thirdPassHandler?=?new?ThirdPassHandler();//第三關(guān)??
??
????????firstPassHandler.setSecondPassHandler(secondPassHandler);//第一關(guān)的下一關(guān)是第二關(guān)??
????????secondPassHandler.setThirdPassHandler(thirdPassHandler);//第二關(guān)的下一關(guān)是第三關(guān)??
??
????????//說明:因?yàn)榈谌P(guān)是最后一關(guān),因此沒有下一關(guān)??
????????//開始調(diào)用第一關(guān)?每一個(gè)關(guān)卡是否進(jìn)入下一關(guān)卡?在每個(gè)關(guān)卡中判斷??
????????firstPassHandler.handler();??
??
????}??
}??
| 缺點(diǎn)
現(xiàn)有模式的缺點(diǎn):
-
每個(gè)關(guān)卡中都有下一關(guān)的成員變量并且是不一樣的,形成鏈很不方便
-
代碼的擴(kuò)展性非常不好
| 責(zé)任鏈改造
既然每個(gè)關(guān)卡中都有下一關(guān)的成員變量并且是不一樣的,那么我們可以在關(guān)卡上抽象出一個(gè)父類或者接口,然后每個(gè)具體的關(guān)卡去繼承或者實(shí)現(xiàn)。
有了思路,我們先來簡單介紹一下責(zé)任鏈設(shè)計(jì)模式的基本組成:
-
抽象處理者(Handler)角色:?定義一個(gè)處理請求的接口,包含抽象處理方法和一個(gè)后繼連接。
-
具體處理者(Concrete Handler)角色:?實(shí)現(xiàn)抽象處理者的處理方法,判斷能否處理本次請求,如果可以處理請求則處理,否則將該請求轉(zhuǎn)給它的后繼者。
-
客戶類(Client)角色:?創(chuàng)建處理鏈,并向鏈頭的具體處理者對象提交請求,它不關(guān)心處理細(xì)節(jié)和請求的傳遞過程。
圖片
public?abstract?class?AbstractHandler?{??
??
????/**??
?????*?下一關(guān)用當(dāng)前抽象類來接收??
?????*/??
????protected?AbstractHandler?next;??
??
????public?void?setNext(AbstractHandler?next)?{??
????????this.next?=?next;??
????}??
??
????public?abstract?int?handler();??
}??
??
public?class?FirstPassHandler?extends?AbstractHandler{??
??
????private?int?play(){??
????????return?80;??
????}??
??
????@Override??
????public?int?handler(){??
????????System.out.println("第一關(guān)-->FirstPassHandler");??
????????int?score?=?play();??
????????if(score?>=?80){??
????????????//分?jǐn)?shù)>=80?并且存在下一關(guān)才進(jìn)入下一關(guān)??
????????????if(this.next?!=?null){??
????????????????return?this.next.handler();??
????????????}??
????????}??
????????return?score;??
????}??
}??
??
public?class?SecondPassHandler?extends?AbstractHandler{??
??
????private?int?play(){??
????????return?90;??
????}??
??
????public?int?handler(){??
????????System.out.println("第二關(guān)-->SecondPassHandler");??
??
????????int?score?=?play();??
????????if(score?>=?90){??
????????????//分?jǐn)?shù)>=90?并且存在下一關(guān)才進(jìn)入下一關(guān)??
????????????if(this.next?!=?null){??
????????????????return?this.next.handler();??
????????????}??
????????}??
??
????????return?score;??
????}??
}??
??
public?class?ThirdPassHandler?extends?AbstractHandler{??
??
????private?int?play(){??
????????return?95;??
????}??
??
????public?int?handler(){??
????????System.out.println("第三關(guān)-->ThirdPassHandler");??
????????int?score?=?play();??
????????if(score?>=?95){??
????????????//分?jǐn)?shù)>=95?并且存在下一關(guān)才進(jìn)入下一關(guān)??
????????????if(this.next?!=?null){??
????????????????return?this.next.handler();??
????????????}??
????????}??
????????return?score;??
????}??
}??
??
public?class?HandlerClient?{??
????public?static?void?main(String[]?args)?{??
??
????????FirstPassHandler?firstPassHandler?=?new?FirstPassHandler();//第一關(guān)??
????????SecondPassHandler?secondPassHandler?=?new?SecondPassHandler();//第二關(guān)??
????????ThirdPassHandler?thirdPassHandler?=?new?ThirdPassHandler();//第三關(guān)??
??
????????//?和上面沒有更改的客戶端代碼相比,只有這里的set方法發(fā)生變化,其他都是一樣的??
????????firstPassHandler.setNext(secondPassHandler);//第一關(guān)的下一關(guān)是第二關(guān)??
????????secondPassHandler.setNext(thirdPassHandler);//第二關(guān)的下一關(guān)是第三關(guān)??
??
????????//說明:因?yàn)榈谌P(guān)是最后一關(guān),因此沒有下一關(guān)??
??
????????//從第一個(gè)關(guān)卡開始??
????????firstPassHandler.handler();??
??
????}??
}??
| 責(zé)任鏈工廠改造
對于上面的請求鏈,我們也可以把這個(gè)關(guān)系維護(hù)到配置文件中或者一個(gè)枚舉中。我將使用枚舉來教會(huì)大家怎么動(dòng)態(tài)的配置請求鏈并且將每個(gè)請求者形成一條調(diào)用鏈。
微信搜索公眾號:Java項(xiàng)目精選,回復(fù):java 領(lǐng)取資料 。
圖片
public?enum?GatewayEnum?{??
????//?handlerId,?攔截者名稱,全限定類名,preHandlerId,nextHandlerId??
????API_HANDLER(new?GatewayEntity(1,?"api接口限流",?"cn.dgut.design.chain_of_responsibility.GateWay.impl.ApiLimitGatewayHandler",?null,?2)),??
????BLACKLIST_HANDLER(new?GatewayEntity(2,?"黑名單攔截",?"cn.dgut.design.chain_of_responsibility.GateWay.impl.BlacklistGatewayHandler",?1,?3)),??
????SESSION_HANDLER(new?GatewayEntity(3,?"用戶會(huì)話攔截",?"cn.dgut.design.chain_of_responsibility.GateWay.impl.SessionGatewayHandler",?2,?null)),??
????;??
??
????GatewayEntity?gatewayEntity;??
??
????public?GatewayEntity?getGatewayEntity()?{??
????????return?gatewayEntity;??
????}??
??
????GatewayEnum(GatewayEntity?gatewayEntity)?{??
????????this.gatewayEntity?=?gatewayEntity;??
????}??
}??
??
public?class?GatewayEntity?{??
??
????private?String?name;??
??
????private?String?conference;??
??
????private?Integer?handlerId;??
??
????private?Integer?preHandlerId;??
??
????private?Integer?nextHandlerId;??
}??
??
??
public?interface?GatewayDao?{??
??
????/**??
?????*?根據(jù)?handlerId?獲取配置項(xiàng)??
?????*?@param?handlerId??
?????*?@return??
?????*/??
????GatewayEntity?getGatewayEntity(Integer?handlerId);??
??
????/**??
?????*?獲取第一個(gè)處理者??
?????*?@return??
?????*/??
????GatewayEntity?getFirstGatewayEntity();??
}??
??
public?class?GatewayImpl?implements?GatewayDao?{??
??
????/**??
?????*?初始化,將枚舉中配置的handler初始化到map中,方便獲取??
?????*/??
????private?static?Map<Integer,?GatewayEntity>?gatewayEntityMap?=?new?HashMap<>();??
??
????static?{??
????????GatewayEnum[]?values?=?GatewayEnum.values();??
????????for?(GatewayEnum?value?:?values)?{??
????????????GatewayEntity?gatewayEntity?=?value.getGatewayEntity();??
????????????gatewayEntityMap.put(gatewayEntity.getHandlerId(),?gatewayEntity);??
????????}??
????}??
??
????@Override??
????public?GatewayEntity?getGatewayEntity(Integer?handlerId)?{??
????????return?gatewayEntityMap.get(handlerId);??
????}??
??
????@Override??
????public?GatewayEntity?getFirstGatewayEntity()?{??
????????for?(Map.Entry<Integer,?GatewayEntity>?entry?:?gatewayEntityMap.entrySet())?{??
????????????GatewayEntity?value?=?entry.getValue();??
????????????//??沒有上一個(gè)handler的就是第一個(gè)??
????????????if?(value.getPreHandlerId()?==?null)?{??
????????????????return?value;??
????????????}??
????????}??
????????return?null;??
????}??
}??
??
public?class?GatewayHandlerEnumFactory?{??
??
????private?static?GatewayDao?gatewayDao?=?new?GatewayImpl();??
??
????//?提供靜態(tài)方法,獲取第一個(gè)handler??
????public?static?GatewayHandler?getFirstGatewayHandler()?{??
??
????????GatewayEntity?firstGatewayEntity?=?gatewayDao.getFirstGatewayEntity();??
????????GatewayHandler?firstGatewayHandler?=?newGatewayHandler(firstGatewayEntity);??
????????if?(firstGatewayHandler?==?null)?{??
????????????return?null;??
????????}??
??
????????GatewayEntity?tempGatewayEntity?=?firstGatewayEntity;??
????????Integer?nextHandlerId?=?null;??
????????GatewayHandler?tempGatewayHandler?=?firstGatewayHandler;??
????????//?迭代遍歷所有handler,以及將它們鏈接起來??
????????while?((nextHandlerId?=?tempGatewayEntity.getNextHandlerId())?!=?null)?{??
????????????GatewayEntity?gatewayEntity?=?gatewayDao.getGatewayEntity(nextHandlerId);??
????????????GatewayHandler?gatewayHandler?=?newGatewayHandler(gatewayEntity);??
????????????tempGatewayHandler.setNext(gatewayHandler);??
????????????tempGatewayHandler?=?gatewayHandler;??
????????????tempGatewayEntity?=?gatewayEntity;??
????????}??
????//?返回第一個(gè)handler??
????????return?firstGatewayHandler;??
????}??
??
????/**??
?????*?反射實(shí)體化具體的處理者??
?????*?@param?firstGatewayEntity??
?????*?@return??
?????*/??
????private?static?GatewayHandler?newGatewayHandler(GatewayEntity?firstGatewayEntity)?{??
????????//?獲取全限定類名??
????????String?className?=?firstGatewayEntity.getConference();???
????????try?{??
????????????//?根據(jù)全限定類名,加載并初始化該類,即會(huì)初始化該類的靜態(tài)段??
????????????Class<?>?clazz?=?Class.forName(className);??
????????????return?(GatewayHandler)?clazz.newInstance();??
????????}?catch?(ClassNotFoundException?|?IllegalAccessException?|?InstantiationException?e)?{??
????????????e.printStackTrace();??
????????}??
????????return?null;??
????}??
??
??
}??
??
public?class?GetewayClient?{??
????public?static?void?main(String[]?args)?{??
????????GetewayHandler?firstGetewayHandler?=?GetewayHandlerEnumFactory.getFirstGetewayHandler();??
????????firstGetewayHandler.service();??
????}??
}??
結(jié)語
設(shè)計(jì)模式有很多,責(zé)任鏈只是其中的一種,我覺得很有意思,非常值得一學(xué)。設(shè)計(jì)模式確實(shí)是一門藝術(shù),仍需努力呀!
來源:blog.csdn.net/q1472750149/article/
details/121886327
推薦
讀者問:省廳選調(diào) 和 阿里開發(fā)崗怎么選?
3 年開發(fā),不會(huì)循環(huán)刪除 List 中的元素,有這么難么?
PS:因?yàn)楣娞柶脚_(tái)更改了推送規(guī)則,如果不想錯(cuò)過內(nèi)容,記得讀完點(diǎn)一下 “在看” ,加個(gè) “星標(biāo)” ,這樣每次新文章推送才會(huì)第一時(shí)間出現(xiàn)在你的訂閱列表里。 點(diǎn)“在看”支持我們吧!
