那曲檬骨新材料有限公司

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

責任鏈設計模式詳解

jf_ro2CN3Fa ? 來源:芋道源碼 ? 2023-05-22 15:12 ? 次閱讀

什么是責任鏈

使用場景

反例

初步改造

缺點

責任鏈改造

責任鏈工廠改造

結語

最近,我讓團隊內一位成員寫了一個導入功能。他使用了責任鏈模式,代碼堆的非常多,bug 也多,沒有達到我預期的效果。

實際上,針對導入功能,我認為模版方法更合適!為此,隔壁團隊也拿出我們的案例,進行了集體 code review。

學好設計模式,且不要為了練習,強行使用!讓原本 100 行就能實現的功能,寫了 3000 行!對錯暫且不論,我們先一起看看責任鏈設計模式吧!

什么是責任鏈

責任鏈模式是一種行為設計模式, 允許你將請求沿著處理者鏈進行發送。收到請求后, 每個處理者均可對請求進行處理, 或將其傳遞給鏈上的下個處理者。

48129cfe-f723-11ed-90ce-dac502259ad0.png

基于 Spring Boot + MyBatis Plus + Vue & Element 實現的后臺管理系統 + 用戶小程序,支持 RBAC 動態權限、多租戶、數據權限、工作流、三方登錄、支付、短信、商城等功能

項目地址:https://github.com/YunaiV/ruoyi-vue-pro

視頻教程:https://doc.iocoder.cn/video/

使用場景

責任鏈的使用場景還是比較多的:

多條件流程判斷:權限控制

ERP 系統流程審批:總經理、人事經理、項目經理

Java 過濾器的底層實現 Filter

如果不使用該設計模式,那么當需求有所改變時,就會使得代碼臃腫或者難以維護,例如下面的例子。

反例

假設現在有一個闖關游戲,進入下一關的條件是上一關的分數要高于 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)角色: 創建處理鏈,并向鏈頭的具體處理者對象提交請求,它不關心處理細節和請求的傳遞過程。

481c50d2-f723-11ed-90ce-dac502259ad0.png

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();

}
}

責任鏈工廠改造

對于上面的請求鏈,我們也可以把這個關系維護到配置文件中或者一個枚舉中。我將使用枚舉來教會大家怎么動態的配置請求鏈并且將每個請求者形成一條調用鏈。

4825da08-f723-11ed-90ce-dac502259ad0.png

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.Entryentry: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{
//根據全限定類名,加載并初始化該類,即會初始化該類的靜態段
Classclazz=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

文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    為什么使用菊花配置模式對FPGA編程會失?。?/a>

    親愛的先生,我們使用了2個Vertex 5&amp; spartan 6 FPGA,用于使用菊花配置模式對這些FPGA進行編程。1.我們正在使用xilinx Blaster“平臺電纜USB
    發表于 11-07 08:25

    什么是菊花模式 星型模式

    什么是菊花模式 星型模式 菊花模式   菊花
    發表于 12-05 09:00 ?8564次閱讀

    嵌入式系統的知識平臺與平臺模式詳解

    嵌入式系統的知識平臺與平臺模式詳解 知識經濟的時代是一個以知識平臺為中心的市場經濟時代。嵌入式系統領域的產業、科技,已從資本經濟時代封閉
    發表于 03-29 15:09 ?939次閱讀

    電子行業供應責任向前邁出一步

    電子行業供應責任向前邁出一步     瑞士蒙特勒2010年3月11日電 /美通社亞洲/ -- Achilles Information Ltd. 與 Global e-Sustainability Initiati
    發表于 03-11 18:19 ?476次閱讀

    基于MSP430單片機低功耗控制與系統工作模式詳解

    基于MSP430單片機低功耗控制與系統工作模式詳解
    發表于 10-12 15:29 ?11次下載
    基于MSP430單片機低功耗控制與系統工作<b class='flag-5'>模式</b><b class='flag-5'>詳解</b>

    區塊技術有哪些特點_區塊技術應用_區塊技術的工作原理

    本文以區塊為中心,主要介紹了區塊技術的特點、區塊的分類、區塊技術應用以及區塊技術的工作原理進行
    發表于 12-18 09:10 ?2.1w次閱讀

    半導體芯片行業的運作模式是什么(IDM/Fabless/Foundry模式

    本文首先詳解半導體芯片行業的三種運作模式,分別有IDM、Fabless和Foundry模式。其次介紹了半導體芯片及半導體芯片產業重要環節,具體的跟隨小編一起來了解一下。
    的頭像 發表于 05-31 11:25 ?32.7w次閱讀

    C語言設計模式的程序資料合集

    之模板模式,C語言設計模式之工廠模式,C語言設計模式責任
    發表于 11-16 08:00 ?5次下載

    HS6621 串口透傳 模式 - [詳解]

    HS6621串口透傳模式詳解
    發表于 12-08 18:36 ?32次下載
    HS6621 串口透傳 <b class='flag-5'>模式</b> - [<b class='flag-5'>詳解</b>]

    一起看看責任設計模式吧!

    如何解決這個問題,我們可以通過鏈表將每一關連接起來,形成責任的方式,第一關通過后是第二關,第二關通過后是第三關 ....,這樣客戶端就不需要進行多重 if 的判斷了
    的頭像 發表于 07-08 16:25 ?902次閱讀

    什么是責任?

    責任模式是行為模式的一種,它將需要觸發的Handler組成一條,發送者將請求發給的第一個接
    的頭像 發表于 02-16 14:41 ?1012次閱讀

    如何用責任默認優雅地進行參數校驗

    那么有什么更好的參數校驗的方式呢?本文就推薦一種通過責任設計模式來優雅地實現參數的校驗功能,我們通過一個用戶注冊的例子來講明白如何實現。
    的頭像 發表于 04-06 15:00 ?488次閱讀

    設計模式行為型:責任模式

    將請求發送者和請求接受者解耦,讓請求的接受者形成鏈式操作,所有人都能夠接受接受到請求,直到有人處理請求。
    的頭像 發表于 06-06 17:33 ?804次閱讀
    設計<b class='flag-5'>模式</b>行為型:<b class='flag-5'>責任</b><b class='flag-5'>鏈</b><b class='flag-5'>模式</b>

    設計模式責任模式概述

    設計模式是一些被反復使用的、具有普遍性的設計解決方案,它們是在特定情境下對軟件設計問題的成功解決方式的總結和歸納。
    的頭像 發表于 09-27 09:54 ?735次閱讀
    設計<b class='flag-5'>模式</b>之<b class='flag-5'>責任</b><b class='flag-5'>鏈</b><b class='flag-5'>模式</b>概述

    還在自己實現責任?我建議你造輪子之前先看看這個開源項目

    1. 前言 設計模式在軟件開發中被廣泛使用。通過使用設計模式,開發人員可以更加高效地開發出高質量的軟件系統,提高代碼的可讀性、可維護性和可擴展性。 責任
    的頭像 發表于 09-20 14:38 ?405次閱讀
    還在自己實現<b class='flag-5'>責任</b><b class='flag-5'>鏈</b>?我建議你造輪子之前先看看這個開源項目
    奥斯卡百家乐官网的玩法技巧和规则| 拜城县| 二八杠自行车| 总玩百家乐有赢的吗| 百家乐博彩的玩法技巧和规则| 同花顺百家乐的玩法技巧和规则 | 澳门顶级赌场317| 娱乐场游戏| 云顶国际平台| 竞咪百家乐的玩法技巧和规则| 百家乐智能系统| 太阳城小区| 银河国际娱乐场| 淘金盈国际线上娱乐| 波音百家乐官网游戏| 百家乐官网庄家出千内幕| 百家乐官网百家乐官网视频游戏世界| 百家乐官网永利娱乐场开户注册| 伟博百家乐官网娱乐城| 免费百家乐在线| 博狗百家乐的玩法技巧和规则| 大发888下载ylc8| 交口县| 百家乐官网走势图解| 百家乐官网特殊计| 百家乐微笑玩法| 大发888客户端| 屏山县| 百家乐官网押注方法| 赌场百家乐投注公式| 缅甸百家乐赌| www.18lk.com| 网上玩百家乐官网会出签吗| 玩百家乐保时捷娱乐城| 威尼斯人娱乐诚| 电子百家乐规则| 千亿国际娱乐城| 在线百家乐官网游戏软件| 百家乐是咋玩法| 大发888客服电话多少| 百家乐官网体育直播|