什么是責(zé)任鏈
使用場(chǎng)景
反例
初步改造
缺點(diǎn)
責(zé)任鏈改造
責(zé)任鏈工廠改造
結(jié)語
最近,我讓團(tuán)隊(duì)內(nèi)一位成員寫了一個(gè)導(dǎo)入功能。他使用了責(zé)任鏈模式,代碼堆的非常多,bug 也多,沒有達(dá)到我預(yù)期的效果。
實(shí)際上,針對(duì)導(dǎo)入功能,我認(rèn)為模版方法更合適!為此,隔壁團(tuán)隊(duì)也拿出我們的案例,進(jìn)行了集體 code review。
學(xué)好設(shè)計(jì)模式,且不要為了練習(xí),強(qiáng)行使用!讓原本 100 行就能實(shí)現(xiàn)的功能,寫了 3000 行!對(duì)錯(cuò)暫且不論,我們先一起看看責(zé)任鏈設(shè)計(jì)模式吧!
什么是責(zé)任鏈
責(zé)任鏈模式是一種行為設(shè)計(jì)模式, 允許你將請(qǐng)求沿著處理者鏈進(jìn)行發(fā)送。收到請(qǐng)求后, 每個(gè)處理者均可對(duì)請(qǐng)求進(jìn)行處理, 或?qū)⑵鋫鬟f給鏈上的下個(gè)處理者。
基于 Spring Boot + MyBatis Plus + Vue & Element 實(shí)現(xiàn)的后臺(tái)管理系統(tǒng) + 用戶小程序,支持 RBAC 動(dòng)態(tài)權(quán)限、多租戶、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能
項(xiàng)目地址:https://github.com/YunaiV/ruoyi-vue-pro
視頻教程:https://doc.iocoder.cn/video/
使用場(chǎng)景
責(zé)任鏈的使用場(chǎng)景還是比較多的:
多條件流程判斷:權(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) 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則進(jìn)入第二關(guān) if(firstScore>=80){ intsecondScore=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ì)對(duì)代碼非常大的改動(dòng),這種操作的風(fēng)險(xiǎn)是很高的,因此,該寫法非常糟糕。
初步改造
如何解決這個(gè)問題,我們可以通過鏈表將每一關(guān)連接起來,形成責(zé)任鏈的方式,第一關(guān)通過后是第二關(guān),第二關(guān)通過后是第三關(guān)....
這樣客戶端就不需要進(jì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)才進(jì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)才進(jì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) //說明:因?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)。
有了思路,我們先來簡(jiǎn)單介紹一下責(zé)任鏈設(shè)計(jì)模式的基本組成:
抽象處理者(Handler)角色: 定義一個(gè)處理請(qǐng)求的接口,包含抽象處理方法和一個(gè)后繼連接。
具體處理者(Concrete Handler)角色: 實(shí)現(xiàn)抽象處理者的處理方法,判斷能否處理本次請(qǐng)求,如果可以處理請(qǐng)求則處理,否則將該請(qǐng)求轉(zhuǎn)給它的后繼者。
客戶類(Client)角色: 創(chuàng)建處理鏈,并向鏈頭的具體處理者對(duì)象提交請(qǐng)求,它不關(guān)心處理細(xì)節(jié)和請(qǐng)求的傳遞過程。
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)才進(jì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)才進(jì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)才進(jì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) //說明:因?yàn)榈谌P(guān)是最后一關(guān),因此沒有下一關(guān) //從第一個(gè)關(guān)卡開始 firstPassHandler.handler(); } }
責(zé)任鏈工廠改造
對(duì)于上面的請(qǐng)求鏈,我們也可以把這個(gè)關(guān)系維護(hù)到配置文件中或者一個(gè)枚舉中。我將使用枚舉來教會(huì)大家怎么動(dòng)態(tài)的配置請(qǐng)求鏈并且將每個(gè)請(qǐng)求者形成一條調(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,"用戶會(huì)話攔截","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獲取配置項(xiàng) *@paramhandlerId *@return */ GatewayEntitygetGatewayEntity(IntegerhandlerId); /** *獲取第一個(gè)處理者 *@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(); //沒有上一個(gè)handler的就是第一個(gè) if(value.getPreHandlerId()==null){ returnvalue; } } returnnull; } } publicclassGatewayHandlerEnumFactory{ privatestaticGatewayDaogatewayDao=newGatewayImpl(); //提供靜態(tài)方法,獲取第一個(gè)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; } //返回第一個(gè)handler returnfirstGatewayHandler; } /** *反射實(shí)體化具體的處理者 *@paramfirstGatewayEntity *@return */ privatestaticGatewayHandlernewGatewayHandler(GatewayEntityfirstGatewayEntity){ //獲取全限定類名 StringclassName=firstGatewayEntity.getConference(); try{ //根據(jù)全限定類名,加載并初始化該類,即會(huì)初始化該類的靜態(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 實(shí)現(xiàn)的后臺(tái)管理系統(tǒng) + 用戶小程序,支持 RBAC 動(dòng)態(tài)權(quán)限、多租戶、數(shù)據(jù)權(quán)限、工作流、三方登錄、支付、短信、商城等功能
項(xiàng)目地址:https://github.com/YunaiV/yudao-cloud
視頻教程:https://doc.iocoder.cn/video/
結(jié)語
設(shè)計(jì)模式有很多,責(zé)任鏈只是其中的一種,我覺得很有意思,非常值得一學(xué)。設(shè)計(jì)模式確實(shí)是一門藝術(shù),仍需努力呀!
-
程序
+關(guān)注
關(guān)注
116文章
3761瀏覽量
80754 -
代碼
+關(guān)注
關(guān)注
30文章
4722瀏覽量
68236 -
RBAC
+關(guān)注
關(guān)注
0文章
43瀏覽量
9933
原文標(biāo)題:代碼精簡(jiǎn)10倍,責(zé)任鏈模式y(tǒng)yds
文章出處:【微信號(hào):芋道源碼,微信公眾號(hào):芋道源碼】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論