什么是責(zé)任鏈
使用場景
反例
初步改造
缺點
責(zé)任鏈改造
責(zé)任鏈工廠改造
結(jié)語
最近,我讓團隊內(nèi)一位成員寫了一個導(dǎo)入功能。他使用了責(zé)任鏈模式,代碼堆的非常多,bug 也多,沒有達到我預(yù)期的效果。
實際上,針對導(dǎo)入功能,我認(rèn)為模版方法更合適!為此,隔壁團隊也拿出我們的案例,進行了集體 code review。
學(xué)好設(shè)計模式,且不要為了練習(xí),強行使用!讓原本 100 行就能實現(xiàn)的功能,寫了 3000 行!對錯暫且不論,我們先一起看看責(zé)任鏈設(shè)計模式吧!
什么是責(zé)任鏈
責(zé)任鏈模式是一種行為設(shè)計模式, 允許你將請求沿著處理者鏈進行發(fā)送。收到請求后, 每個處理者均可對請求進行處理, 或?qū)⑵鋫鬟f給鏈上的下個處理者。
基于 Spring Boot + MyBatis Plus + Vue & Element 實現(xiàn)的后臺管理系統(tǒng) + 用戶小程序,支持 RBAC 動態(tài)權(quán)限、多租戶、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能
項目地址:https://github.com/YunaiV/ruoyi-vue-pro
視頻教程:https://doc.iocoder.cn/video/
使用場景
責(zé)任鏈的使用場景還是比較多的:
多條件流程判斷:權(quán)限控制
ERP 系統(tǒng)流程審批:總經(jīng)理、人事經(jīng)理、項目經(jīng)理
如果不使用該設(shè)計模式,那么當(dāng)需求有所改變時,就會使得代碼臃腫或者難以維護,例如下面的例子。
反例
假設(shè)現(xiàn)在有一個闖關(guān)游戲,進入下一關(guān)的條件是上一關(guān)的分?jǐn)?shù)要高于 xx:
游戲一共 3 個關(guān)卡
進入第二關(guān)需要第一關(guān)的游戲得分大于等于 80
進入第三關(guān)需要第二關(guān)的游戲得分大于等于 90
那么代碼可以這樣寫:
//第一關(guān) publicclassFirstPassHandler{ publicinthandler(){ System.out.println("第一關(guān)-->FirstPassHandler"); return80; } } //第二關(guān) publicclassSecondPassHandler{ publicinthandler(){ System.out.println("第二關(guān)-->SecondPassHandler"); return90; } } //第三關(guān) publicclassThirdPassHandler{ publicinthandler(){ System.out.println("第三關(guān)-->ThirdPassHandler,這是最后一關(guān)啦"); return95; } } //客戶端 publicclassHandlerClient{ publicstaticvoidmain(String[]args){ FirstPassHandlerfirstPassHandler=newFirstPassHandler();//第一關(guān) SecondPassHandlersecondPassHandler=newSecondPassHandler();//第二關(guān) ThirdPassHandlerthirdPassHandler=newThirdPassHandler();//第三關(guān) intfirstScore=firstPassHandler.handler(); //第一關(guān)的分?jǐn)?shù)大于等于80則進入第二關(guān) if(firstScore>=80){ intsecondScore=secondPassHandler.handler(); //第二關(guān)的分?jǐn)?shù)大于等于90則進入第二關(guān) if(secondScore>=90){ thirdPassHandler.handler(); } } } }
那么如果這個游戲有 100 關(guān),我們的代碼很可能就會寫成這個樣子:
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)進行調(diào)整時會對代碼非常大的改動,這種操作的風(fēng)險是很高的,因此,該寫法非常糟糕。
初步改造
如何解決這個問題,我們可以通過鏈表將每一關(guān)連接起來,形成責(zé)任鏈的方式,第一關(guān)通過后是第二關(guān),第二關(guān)通過后是第三關(guān)....
這樣客戶端就不需要進行多重 if 的判斷了:
publicclassFirstPassHandler{ /** *第一關(guān)的下一關(guān)是第二關(guān) */ privateSecondPassHandlersecondPassHandler; publicvoidsetSecondPassHandler(SecondPassHandlersecondPassHandler){ this.secondPassHandler=secondPassHandler; } //本關(guān)卡游戲得分 privateintplay(){ return80; } publicinthandler(){ System.out.println("第一關(guān)-->FirstPassHandler"); if(play()>=80){ //分?jǐn)?shù)>=80并且存在下一關(guān)才進入下一關(guān) if(this.secondPassHandler!=null){ returnthis.secondPassHandler.handler(); } } return80; } } publicclassSecondPassHandler{ /** *第二關(guān)的下一關(guān)是第三關(guān) */ privateThirdPassHandlerthirdPassHandler; publicvoidsetThirdPassHandler(ThirdPassHandlerthirdPassHandler){ this.thirdPassHandler=thirdPassHandler; } //本關(guān)卡游戲得分 privateintplay(){ return90; } publicinthandler(){ System.out.println("第二關(guān)-->SecondPassHandler"); if(play()>=90){ //分?jǐn)?shù)>=90并且存在下一關(guān)才進入下一關(guān) if(this.thirdPassHandler!=null){ returnthis.thirdPassHandler.handler(); } } return90; } } publicclassThirdPassHandler{ //本關(guān)卡游戲得分 privateintplay(){ return95; } /** *這是最后一關(guān),因此沒有下一關(guān) */ publicinthandler(){ System.out.println("第三關(guān)-->ThirdPassHandler,這是最后一關(guān)啦"); returnplay(); } } publicclassHandlerClient{ publicstaticvoidmain(String[]args){ FirstPassHandlerfirstPassHandler=newFirstPassHandler();//第一關(guān) SecondPassHandlersecondPassHandler=newSecondPassHandler();//第二關(guān) ThirdPassHandlerthirdPassHandler=newThirdPassHandler();//第三關(guān) firstPassHandler.setSecondPassHandler(secondPassHandler);//第一關(guān)的下一關(guān)是第二關(guān) secondPassHandler.setThirdPassHandler(thirdPassHandler);//第二關(guān)的下一關(guān)是第三關(guān) //說明:因為第三關(guān)是最后一關(guān),因此沒有下一關(guān) //開始調(diào)用第一關(guān)每一個關(guān)卡是否進入下一關(guān)卡在每個關(guān)卡中判斷 firstPassHandler.handler(); } }
缺點
現(xiàn)有模式的缺點:
每個關(guān)卡中都有下一關(guān)的成員變量并且是不一樣的,形成鏈很不方便
代碼的擴展性非常不好
責(zé)任鏈改造
既然每個關(guān)卡中都有下一關(guān)的成員變量并且是不一樣的,那么我們可以在關(guān)卡上抽象出一個父類或者接口,然后每個具體的關(guān)卡去繼承或者實現(xiàn)。
有了思路,我們先來簡單介紹一下責(zé)任鏈設(shè)計模式的基本組成:
抽象處理者(Handler)角色: 定義一個處理請求的接口,包含抽象處理方法和一個后繼連接。
具體處理者(Concrete Handler)角色: 實現(xiàn)抽象處理者的處理方法,判斷能否處理本次請求,如果可以處理請求則處理,否則將該請求轉(zhuǎn)給它的后繼者。
客戶類(Client)角色: 創(chuàng)建處理鏈,并向鏈頭的具體處理者對象提交請求,它不關(guān)心處理細(xì)節(jié)和請求的傳遞過程。
publicabstractclassAbstractHandler{ /** *下一關(guān)用當(dāng)前抽象類來接收 */ protectedAbstractHandlernext; publicvoidsetNext(AbstractHandlernext){ this.next=next; } publicabstractinthandler(); } publicclassFirstPassHandlerextendsAbstractHandler{ privateintplay(){ return80; } @Override publicinthandler(){ System.out.println("第一關(guān)-->FirstPassHandler"); intscore=play(); if(score>=80){ //分?jǐn)?shù)>=80并且存在下一關(guān)才進入下一關(guān) if(this.next!=null){ returnthis.next.handler(); } } returnscore; } } publicclassSecondPassHandlerextendsAbstractHandler{ privateintplay(){ return90; } publicinthandler(){ System.out.println("第二關(guān)-->SecondPassHandler"); intscore=play(); if(score>=90){ //分?jǐn)?shù)>=90并且存在下一關(guān)才進入下一關(guān) if(this.next!=null){ returnthis.next.handler(); } } returnscore; } } publicclassThirdPassHandlerextendsAbstractHandler{ privateintplay(){ return95; } publicinthandler(){ System.out.println("第三關(guān)-->ThirdPassHandler"); intscore=play(); if(score>=95){ //分?jǐn)?shù)>=95并且存在下一關(guān)才進入下一關(guān) if(this.next!=null){ returnthis.next.handler(); } } returnscore; } } publicclassHandlerClient{ publicstaticvoidmain(String[]args){ FirstPassHandlerfirstPassHandler=newFirstPassHandler();//第一關(guān) SecondPassHandlersecondPassHandler=newSecondPassHandler();//第二關(guān) ThirdPassHandlerthirdPassHandler=newThirdPassHandler();//第三關(guān) //和上面沒有更改的客戶端代碼相比,只有這里的set方法發(fā)生變化,其他都是一樣的 firstPassHandler.setNext(secondPassHandler);//第一關(guān)的下一關(guān)是第二關(guān) secondPassHandler.setNext(thirdPassHandler);//第二關(guān)的下一關(guān)是第三關(guān) //說明:因為第三關(guān)是最后一關(guān),因此沒有下一關(guān) //從第一個關(guān)卡開始 firstPassHandler.handler(); } }
責(zé)任鏈工廠改造
對于上面的請求鏈,我們也可以把這個關(guān)系維護到配置文件中或者一個枚舉中。我將使用枚舉來教會大家怎么動態(tài)的配置請求鏈并且將每個請求者形成一條調(diào)用鏈。
publicenumGatewayEnum{ //handlerId,攔截者名稱,全限定類名,preHandlerId,nextHandlerId API_HANDLER(newGatewayEntity(1,"api接口限流","cn.dgut.design.chain_of_responsibility.GateWay.impl.ApiLimitGatewayHandler",null,2)), BLACKLIST_HANDLER(newGatewayEntity(2,"黑名單攔截","cn.dgut.design.chain_of_responsibility.GateWay.impl.BlacklistGatewayHandler",1,3)), SESSION_HANDLER(newGatewayEntity(3,"用戶會話攔截","cn.dgut.design.chain_of_responsibility.GateWay.impl.SessionGatewayHandler",2,null)), ; GatewayEntitygatewayEntity; publicGatewayEntitygetGatewayEntity(){ returngatewayEntity; } GatewayEnum(GatewayEntitygatewayEntity){ this.gatewayEntity=gatewayEntity; } } publicclassGatewayEntity{ privateStringname; privateStringconference; privateIntegerhandlerId; privateIntegerpreHandlerId; privateIntegernextHandlerId; } publicinterfaceGatewayDao{ /** *根據(jù)handlerId獲取配置項 *@paramhandlerId *@return */ GatewayEntitygetGatewayEntity(IntegerhandlerId); /** *獲取第一個處理者 *@return */ GatewayEntitygetFirstGatewayEntity(); } publicclassGatewayImplimplementsGatewayDao{ /** *初始化,將枚舉中配置的handler初始化到map中,方便獲取 */ privatestaticMapgatewayEntityMap=newHashMap<>(); static{ GatewayEnum[]values=GatewayEnum.values(); for(GatewayEnumvalue:values){ GatewayEntitygatewayEntity=value.getGatewayEntity(); gatewayEntityMap.put(gatewayEntity.getHandlerId(),gatewayEntity); } } @Override publicGatewayEntitygetGatewayEntity(IntegerhandlerId){ returngatewayEntityMap.get(handlerId); } @Override publicGatewayEntitygetFirstGatewayEntity(){ for(Map.Entry entry:gatewayEntityMap.entrySet()){ GatewayEntityvalue=entry.getValue(); //沒有上一個handler的就是第一個 if(value.getPreHandlerId()==null){ returnvalue; } } returnnull; } } publicclassGatewayHandlerEnumFactory{ privatestaticGatewayDaogatewayDao=newGatewayImpl(); //提供靜態(tài)方法,獲取第一個handler publicstaticGatewayHandlergetFirstGatewayHandler(){ GatewayEntityfirstGatewayEntity=gatewayDao.getFirstGatewayEntity(); GatewayHandlerfirstGatewayHandler=newGatewayHandler(firstGatewayEntity); if(firstGatewayHandler==null){ returnnull; } GatewayEntitytempGatewayEntity=firstGatewayEntity; IntegernextHandlerId=null; GatewayHandlertempGatewayHandler=firstGatewayHandler; //迭代遍歷所有handler,以及將它們鏈接起來 while((nextHandlerId=tempGatewayEntity.getNextHandlerId())!=null){ GatewayEntitygatewayEntity=gatewayDao.getGatewayEntity(nextHandlerId); GatewayHandlergatewayHandler=newGatewayHandler(gatewayEntity); tempGatewayHandler.setNext(gatewayHandler); tempGatewayHandler=gatewayHandler; tempGatewayEntity=gatewayEntity; } //返回第一個handler returnfirstGatewayHandler; } /** *反射實體化具體的處理者 *@paramfirstGatewayEntity *@return */ privatestaticGatewayHandlernewGatewayHandler(GatewayEntityfirstGatewayEntity){ //獲取全限定類名 StringclassName=firstGatewayEntity.getConference(); try{ //根據(jù)全限定類名,加載并初始化該類,即會初始化該類的靜態(tài)段 Class>clazz=Class.forName(className); return(GatewayHandler)clazz.newInstance(); }catch(ClassNotFoundException|IllegalAccessException|InstantiationExceptione){ e.printStackTrace(); } returnnull; } } publicclassGetewayClient{ publicstaticvoidmain(String[]args){ GetewayHandlerfirstGetewayHandler=GetewayHandlerEnumFactory.getFirstGetewayHandler(); firstGetewayHandler.service(); } }
基于 Spring Cloud Alibaba + Gateway + Nacos + RocketMQ + Vue & Element 實現(xiàn)的后臺管理系統(tǒng) + 用戶小程序,支持 RBAC 動態(tài)權(quán)限、多租戶、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能
項目地址:https://github.com/YunaiV/yudao-cloud
視頻教程:https://doc.iocoder.cn/video/
結(jié)語
設(shè)計模式有很多,責(zé)任鏈只是其中的一種,我覺得很有意思,非常值得一學(xué)。設(shè)計模式確實是一門藝術(shù),仍需努力呀!
-
程序
+關(guān)注
關(guān)注
117文章
3797瀏覽量
81450 -
代碼
+關(guān)注
關(guān)注
30文章
4830瀏覽量
69111 -
RBAC
+關(guān)注
關(guān)注
0文章
44瀏覽量
9994
原文標(biāo)題:代碼精簡10倍,責(zé)任鏈模式y(tǒng)yds
文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
為什么使用菊花鏈配置模式對FPGA編程會失???
嵌入式系統(tǒng)的知識平臺與平臺模式詳解
電子行業(yè)供應(yīng)鏈責(zé)任向前邁出一步
區(qū)塊鏈技術(shù)有哪些特點_區(qū)塊鏈技術(shù)應(yīng)用_區(qū)塊鏈技術(shù)的工作原理
半導(dǎo)體芯片行業(yè)的運作模式是什么(IDM/Fabless/Foundry模式)
一起看看責(zé)任鏈設(shè)計模式吧!
什么是責(zé)任鏈?
如何用責(zé)任鏈默認(rèn)優(yōu)雅地進行參數(shù)校驗
設(shè)計模式行為型:責(zé)任鏈模式
設(shè)計模式之責(zé)任鏈模式概述
![設(shè)計<b class='flag-5'>模式</b>之<b class='flag-5'>責(zé)任</b><b class='flag-5'>鏈</b><b class='flag-5'>模式</b>概述](https://file1.elecfans.com/web2/M00/A8/0C/wKgZomUTi82ACqPNAAEZAomBLos665.jpg)
還在自己實現(xiàn)責(zé)任鏈?我建議你造輪子之前先看看這個開源項目
![還在自己實現(xiàn)<b class='flag-5'>責(zé)任</b><b class='flag-5'>鏈</b>?我建議你造輪子之前先看看這個開源項目](https://file1.elecfans.com//web2/M00/07/FF/wKgaombtGFuAFlFjAAMfA3tGG9c190.png)
評論