什么是責任鏈
使用場景
反例
初步改造
缺點
責任鏈改造
責任鏈工廠改造
結語
最近,我讓團隊內一位成員寫了一個導入功能。他使用了責任鏈模式,代碼堆的非常多,bug 也多,沒有達到我預期的效果。
實際上,針對導入功能,我認為模版方法更合適!為此,隔壁團隊也拿出我們的案例,進行了集體 code review。
學好設計模式,且不要為了練習,強行使用!讓原本 100 行就能實現的功能,寫了 3000 行!對錯暫且不論,我們先一起看看責任鏈設計模式吧!
什么是責任鏈
責任鏈模式是一種行為設計模式, 允許你將請求沿著處理者鏈進行發送。收到請求后, 每個處理者均可對請求進行處理, 或將其傳遞給鏈上的下個處理者。
基于 Spring Boot + MyBatis Plus + Vue & Element 實現的后臺管理系統 + 用戶小程序,支持 RBAC 動態權限、多租戶、數據權限、工作流、三方登錄、支付、短信、商城等功能
項目地址:https://github.com/YunaiV/ruoyi-vue-pro
視頻教程:https://doc.iocoder.cn/video/
使用場景
責任鏈的使用場景還是比較多的:
多條件流程判斷:權限控制
ERP 系統流程審批:總經理、人事經理、項目經理
如果不使用該設計模式,那么當需求有所改變時,就會使得代碼臃腫或者難以維護,例如下面的例子。
反例
假設現在有一個闖關游戲,進入下一關的條件是上一關的分數要高于 xx:
游戲一共 3 個關卡
進入第二關需要第一關的游戲得分大于等于 80
進入第三關需要第二關的游戲得分大于等于 90
那么代碼可以這樣寫:
//第一關 publicclassFirstPassHandler{ publicinthandler(){ System.out.println("第一關-->FirstPassHandler"); return80; } } //第二關 publicclassSecondPassHandler{ publicinthandler(){ System.out.println("第二關-->SecondPassHandler"); return90; } } //第三關 publicclassThirdPassHandler{ publicinthandler(){ System.out.println("第三關-->ThirdPassHandler,這是最后一關啦"); return95; } } //客戶端 publicclassHandlerClient{ publicstaticvoidmain(String[]args){ FirstPassHandlerfirstPassHandler=newFirstPassHandler();//第一關 SecondPassHandlersecondPassHandler=newSecondPassHandler();//第二關 ThirdPassHandlerthirdPassHandler=newThirdPassHandler();//第三關 intfirstScore=firstPassHandler.handler(); //第一關的分數大于等于80則進入第二關 if(firstScore>=80){ intsecondScore=secondPassHandler.handler(); //第二關的分數大于等于90則進入第二關 if(secondScore>=90){ thirdPassHandler.handler(); } } } }
那么如果這個游戲有 100 關,我們的代碼很可能就會寫成這個樣子:
if(第1關通過){ //第2關游戲 if(第2關通過){ //第3關游戲 if(第3關通過){ //第4關游戲 if(第4關通過){ //第5關游戲 if(第5關通過){ //第6關游戲 if(第6關通過){ //... } } } } } }
這種代碼不僅冗余,并且當我們要將某兩關進行調整時會對代碼非常大的改動,這種操作的風險是很高的,因此,該寫法非常糟糕。
初步改造
如何解決這個問題,我們可以通過鏈表將每一關連接起來,形成責任鏈的方式,第一關通過后是第二關,第二關通過后是第三關....
這樣客戶端就不需要進行多重 if 的判斷了:
publicclassFirstPassHandler{ /** *第一關的下一關是第二關 */ privateSecondPassHandlersecondPassHandler; publicvoidsetSecondPassHandler(SecondPassHandlersecondPassHandler){ this.secondPassHandler=secondPassHandler; } //本關卡游戲得分 privateintplay(){ return80; } publicinthandler(){ System.out.println("第一關-->FirstPassHandler"); if(play()>=80){ //分數>=80并且存在下一關才進入下一關 if(this.secondPassHandler!=null){ returnthis.secondPassHandler.handler(); } } return80; } } publicclassSecondPassHandler{ /** *第二關的下一關是第三關 */ privateThirdPassHandlerthirdPassHandler; publicvoidsetThirdPassHandler(ThirdPassHandlerthirdPassHandler){ this.thirdPassHandler=thirdPassHandler; } //本關卡游戲得分 privateintplay(){ return90; } publicinthandler(){ System.out.println("第二關-->SecondPassHandler"); if(play()>=90){ //分數>=90并且存在下一關才進入下一關 if(this.thirdPassHandler!=null){ returnthis.thirdPassHandler.handler(); } } return90; } } publicclassThirdPassHandler{ //本關卡游戲得分 privateintplay(){ return95; } /** *這是最后一關,因此沒有下一關 */ publicinthandler(){ System.out.println("第三關-->ThirdPassHandler,這是最后一關啦"); returnplay(); } } publicclassHandlerClient{ publicstaticvoidmain(String[]args){ FirstPassHandlerfirstPassHandler=newFirstPassHandler();//第一關 SecondPassHandlersecondPassHandler=newSecondPassHandler();//第二關 ThirdPassHandlerthirdPassHandler=newThirdPassHandler();//第三關 firstPassHandler.setSecondPassHandler(secondPassHandler);//第一關的下一關是第二關 secondPassHandler.setThirdPassHandler(thirdPassHandler);//第二關的下一關是第三關 //說明:因為第三關是最后一關,因此沒有下一關 //開始調用第一關每一個關卡是否進入下一關卡在每個關卡中判斷 firstPassHandler.handler(); } }
缺點
現有模式的缺點:
每個關卡中都有下一關的成員變量并且是不一樣的,形成鏈很不方便
代碼的擴展性非常不好
責任鏈改造
既然每個關卡中都有下一關的成員變量并且是不一樣的,那么我們可以在關卡上抽象出一個父類或者接口,然后每個具體的關卡去繼承或者實現。
有了思路,我們先來簡單介紹一下責任鏈設計模式的基本組成:
抽象處理者(Handler)角色: 定義一個處理請求的接口,包含抽象處理方法和一個后繼連接。
具體處理者(Concrete Handler)角色: 實現抽象處理者的處理方法,判斷能否處理本次請求,如果可以處理請求則處理,否則將該請求轉給它的后繼者。
客戶類(Client)角色: 創建處理鏈,并向鏈頭的具體處理者對象提交請求,它不關心處理細節和請求的傳遞過程。
publicabstractclassAbstractHandler{ /** *下一關用當前抽象類來接收 */ protectedAbstractHandlernext; publicvoidsetNext(AbstractHandlernext){ this.next=next; } publicabstractinthandler(); } publicclassFirstPassHandlerextendsAbstractHandler{ privateintplay(){ return80; } @Override publicinthandler(){ System.out.println("第一關-->FirstPassHandler"); intscore=play(); if(score>=80){ //分數>=80并且存在下一關才進入下一關 if(this.next!=null){ returnthis.next.handler(); } } returnscore; } } publicclassSecondPassHandlerextendsAbstractHandler{ privateintplay(){ return90; } publicinthandler(){ System.out.println("第二關-->SecondPassHandler"); intscore=play(); if(score>=90){ //分數>=90并且存在下一關才進入下一關 if(this.next!=null){ returnthis.next.handler(); } } returnscore; } } publicclassThirdPassHandlerextendsAbstractHandler{ privateintplay(){ return95; } publicinthandler(){ System.out.println("第三關-->ThirdPassHandler"); intscore=play(); if(score>=95){ //分數>=95并且存在下一關才進入下一關 if(this.next!=null){ returnthis.next.handler(); } } returnscore; } } publicclassHandlerClient{ publicstaticvoidmain(String[]args){ FirstPassHandlerfirstPassHandler=newFirstPassHandler();//第一關 SecondPassHandlersecondPassHandler=newSecondPassHandler();//第二關 ThirdPassHandlerthirdPassHandler=newThirdPassHandler();//第三關 //和上面沒有更改的客戶端代碼相比,只有這里的set方法發生變化,其他都是一樣的 firstPassHandler.setNext(secondPassHandler);//第一關的下一關是第二關 secondPassHandler.setNext(thirdPassHandler);//第二關的下一關是第三關 //說明:因為第三關是最后一關,因此沒有下一關 //從第一個關卡開始 firstPassHandler.handler(); } }
責任鏈工廠改造
對于上面的請求鏈,我們也可以把這個關系維護到配置文件中或者一個枚舉中。我將使用枚舉來教會大家怎么動態的配置請求鏈并且將每個請求者形成一條調用鏈。
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{ /** *根據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(); //提供靜態方法,獲取第一個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{ //根據全限定類名,加載并初始化該類,即會初始化該類的靜態段 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 實現的后臺管理系統 + 用戶小程序,支持 RBAC 動態權限、多租戶、數據權限、工作流、三方登錄、支付、短信、商城等功能
項目地址:https://github.com/YunaiV/yudao-cloud
視頻教程:https://doc.iocoder.cn/video/
結語
設計模式有很多,責任鏈只是其中的一種,我覺得很有意思,非常值得一學。設計模式確實是一門藝術,仍需努力呀!
-
程序
+關注
關注
117文章
3795瀏覽量
81402 -
代碼
+關注
關注
30文章
4825瀏覽量
69036 -
RBAC
+關注
關注
0文章
44瀏覽量
9987
原文標題:代碼精簡10倍,責任鏈模式yyds
文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論