責任鏈模式:構建靈活可擴展的請求處理體系(Java 實現詳解)

一、責任鏈模式核心概念解析

(一)模式定義與本質

責任鏈模式(Chain of Responsibility Pattern)是一種行為型設計模式,其核心思想是將多個處理者對象連成一條鏈,并沿著這條鏈傳遞請求,直到有某個處理者對象處理它為止。這種模式通過將請求的發送者和接收者解耦,使得多個對象都有機會處理請求,從而避免請求發送者與具體處理者之間的緊耦合。

責任鏈模式的本質可以概括為:傳遞請求,推卸責任。每個處理者只負責處理自己職責范圍內的請求,對于超出職責范圍的請求,則將其傳遞給鏈中的下一個處理者,形成一個靈活請求處理鏈條。

(二)核心應用場景

  1. 多級審批流程:如請假審批、費用報銷審批等,不同級別的審批者處理不同額度或類型的請求。
  2. 過濾鏈場景:例如日志處理中的多級過濾、HTTP 請求的過濾器鏈。
  3. 錯誤處理鏈:在復雜系統中,不同類型的錯誤由不同的處理器進行處理。
  4. 事件處理機制:圖形界面開發中,事件可能需要多個組件依次處理,如按鈕點擊事件可能先由按鈕處理,再傳遞給容器組件。

(三)模式優缺點分析

優點

  • 解耦請求發送者與處理者:發送者無需知道具體哪個處理者處理請求,只需將請求發送到鏈上。
  • 動態靈活:可以在運行時動態調整責任鏈的順序,新增或刪除處理者,而無需修改客戶端代碼。
  • 符合開閉原則:新增處理者只需實現接口并加入鏈中,不影響現有代碼。

缺點

  • 請求可能未被處理:如果責任鏈中沒有處理者能夠處理該請求,可能導致請求丟失,需要在鏈尾設置默認處理者。
  • 調試難度增加:請求的處理路徑可能較長,調試時需要跟蹤整個鏈的處理過程。
  • 性能開銷:每個請求都需要沿著鏈進行傳遞,可能會帶來一定的性能損失,尤其是在鏈較長時。

二、責任鏈模式的核心角色與 UML 結構

(一)四大核心角色

  1. 抽象處理者(Handler):定義處理請求的接口,包含一個指向后繼處理者的引用,并實現默認的請求傳遞方法(如設置下一個處理者、傳遞請求等)。
  2. 具體處理者(Concrete Handler):實現抽象處理者定義的處理方法,判斷是否能夠處理當前請求,若能則處理,否則將請求傳遞給后繼處理者。
  3. 請求對象(Request):封裝請求的相關信息,供處理者判斷是否需要處理。
  4. 客戶端(Client):創建責任鏈,并向鏈的頭部發送請求。

(二)UML 類圖結構

plantuml

@startuml
class Client{- Handler headHandler+ void sendRequest(Request request)
}
class Handler{- Handler nextHandler+ setNextHandler(Handler handler)+ handleRequest(Request request)
}
class ConcreteHandler1{+ handleRequest(Request request)
}
class ConcreteHandler2{+ handleRequest(Request request)
}
class Request{- Object data// getters and setters
}
Client --> Handler
Handler <|-- ConcreteHandler1
Handler <|-- ConcreteHandler2
Handler "1" -- "0..1" Handler : nextHandler
@enduml

(三)關鍵交互流程

  1. 客戶端創建具體處理者實例,并通過setNextHandler方法將處理者連接成鏈,確定鏈的順序。
  2. 客戶端向鏈的頭部處理者(通常是第一個處理者)發送請求。
  3. 頭部處理者接收到請求后,先判斷自己是否能夠處理該請求:
    • 若能處理,則執行具體的處理邏輯,處理完畢后根據需要決定是否繼續傳遞請求(通常處理后不再傳遞)。
    • 若不能處理,則調用nextHandlerhandleRequest方法,將請求傳遞給下一個處理者。
  4. 后續處理者重復上述步驟,直到請求被處理或到達鏈尾。

三、Java 手寫責任鏈模式實現 —— 請假審批系統

(一)業務場景說明

假設我們需要實現一個員工請假審批系統,請假流程如下:

  • 請假天數≤1 天,由直屬領導審批。
  • 1 天 < 請假天數≤3 天,由部門經理審批。
  • 3 天 < 請假天數≤7 天,由總監審批。
  • 請假天數 > 7 天,由總經理審批。

(二)實現步驟詳解

1. 定義請求類(Request)

java

public class LeaveRequest {private String employeeName; // 員工姓名private int days; // 請假天數private String reason; // 請假原因public LeaveRequest(String employeeName, int days, String reason) {this.employeeName = employeeName;this.days = days;this.reason = reason;}// getters and setterspublic String getEmployeeName() { return employeeName; }public int getDays() { return days; }public String getReason() { return reason; }
}
2. 定義抽象處理者(Handler)

java

public abstract class Approver {protected Approver nextApprover; // 后繼處理者// 設置下一個處理者public void setNextApprover(Approver nextApprover) {this.nextApprover = nextApprover;}// 處理請求的抽象方法public abstract void processRequest(LeaveRequest request);
}
3. 實現具體處理者(Concrete Handler)

直屬領導(TeamLeaderApprover)

java

public class TeamLeaderApprover extends Approver {@Overridepublic void processRequest(LeaveRequest request) {if (request.getDays() <= 1) {System.out.println("直屬領導" + this + "審批通過:" + request.getEmployeeName() + "請假" + request.getDays() + "天,原因:" + request.getReason());} else {if (nextApprover != null) {nextApprover.processRequest(request); // 傳遞給下一個處理者} else {System.out.println("請假天數過長,無人審批!");}}}
}

部門經理(DepartmentManagerApprover)

java

public class DepartmentManagerApprover extends Approver {@Overridepublic void processRequest(LeaveRequest request) {if (request.getDays() > 1 && request.getDays() <= 3) {System.out.println("部門經理" + this + "審批通過:" + request.getEmployeeName() + "請假" + request.getDays() + "天,原因:" + request.getReason());} else {if (nextApprover != null) {nextApprover.processRequest(request);} else {System.out.println("請假天數過長,無人審批!");}}}
}

總監(DirectorApprover)

java

public class DirectorApprover extends Approver {@Overridepublic void processRequest(LeaveRequest request) {if (request.getDays() > 3 && request.getDays() <= 7) {System.out.println("總監" + this + "審批通過:" + request.getEmployeeName() + "請假" + request.getDays() + "天,原因:" + request.getReason());} else {if (nextApprover != null) {nextApprover.processRequest(request);} else {System.out.println("請假天數過長,無人審批!");}}}
}

總經理(GeneralManagerApprover)

java

public class GeneralManagerApprover extends Approver {@Overridepublic void processRequest(LeaveRequest request) {if (request.getDays() > 7) {System.out.println("總經理" + this + "審批通過:" + request.getEmployeeName() + "請假" + request.getDays() + "天,原因:" + request.getReason());} else {if (nextApprover != null) {nextApprover.processRequest(request);} else {System.out.println("請假天數過長,無人審批!");}}}
}
4. 客戶端調用(Client)

java

public class Client {public static void main(String[] args) {// 創建處理者實例Approver teamLeader = new TeamLeaderApprover();Approver departmentManager = new DepartmentManagerApprover();Approver director = new DirectorApprover();Approver generalManager = new GeneralManagerApprover();// 構建責任鏈teamLeader.setNextApprover(departmentManager);departmentManager.setNextApprover(director);director.setNextApprover(generalManager);// 創建請求LeaveRequest request1 = new LeaveRequest("張三", 1, "病假");LeaveRequest request2 = new LeaveRequest("李四", 2, "事假");LeaveRequest request3 = new LeaveRequest("王五", 5, "婚假");LeaveRequest request4 = new LeaveRequest("趙六", 10, "產假");// 發送請求teamLeader.processRequest(request1);teamLeader.processRequest(request2);teamLeader.processRequest(request3);teamLeader.processRequest(request4);}
}
5. 運行結果

plaintext

直屬領導TeamLeaderApprover@1540e19d審批通過:張三請假1天,原因:病假
部門經理DepartmentManagerApprover@677327b6審批通過:李四請假2天,原因:事假
總監DirectorApprover@14ae5a5審批通過:王五請假5天,原因:婚假
總經理GeneralManagerApprover@7f31245a審批通過:趙六請假10天,原因:產假

(三)實現關鍵點解析

  1. 處理者鏈的構建:通過setNextApprover方法將處理者依次連接,形成責任鏈。鏈的順序非常重要,決定了請求的處理順序,通常按照處理者的處理能力從小到大或從低到高排列。
  2. 請求處理邏輯:每個具體處理者首先判斷自己是否能處理當前請求,處理邏輯可以是基于請求的屬性(如請假天數)、權限等。若不能處理,則傳遞給下一個處理者,注意要處理nextApprovernull的情況(鏈尾),避免空指針異常。
  3. 抽象處理者的設計:抽象類中定義了后繼處理者的引用和設置方法,以及處理請求的抽象方法,確保所有具體處理者具有一致的接口。

四、責任鏈模式的優化與擴展

(一)帶返回值的責任鏈

在上述案例中,處理者處理請求后通常不會返回結果,但在實際場景中,可能需要處理者返回處理結果(如審批是否通過、處理后的數據等)。此時可以修改processRequest方法,使其返回一個結果對象。

修改抽象處理者

java

public abstract class Approver {// ... 其他代碼不變public abstract ApproveResult processRequest(LeaveRequest request);
}// 審批結果類
public class ApproveResult {private boolean approved;private String message;// 構造方法、getters and setters
}

具體處理者返回結果

java

public class TeamLeaderApprover extends Approver {@Overridepublic ApproveResult processRequest(LeaveRequest request) {ApproveResult result = new ApproveResult();if (request.getDays() <= 1) {result.setApproved(true);result.setMessage("直屬領導審批通過");} else {if (nextApprover != null) {result = nextApprover.processRequest(request); // 傳遞請求并獲取結果} else {result.setApproved(false);result.setMessage("無人審批");}}return result;}
}

(二)動態構建責任鏈

在客戶端硬編碼責任鏈的順序不夠靈活,特別是當鏈的結構可能頻繁變化時。可以通過配置文件(如 XML、JSON)或數據庫來存儲處理者的順序,運行時動態加載并構建責任鏈。

示例:從配置文件加載責任鏈

  1. 創建配置文件approvers.config

properties

approver1=com.example.TeamLeaderApprover
approver2=com.example.DepartmentManagerApprover
approver3=com.example.DirectorApprover
approver4=com.example.GeneralManagerApprover
  1. 客戶端動態加載處理者并構建鏈:

java

public class Client {public static void main(String[] args) throws Exception {Properties properties = new Properties();properties.load(new FileInputStream("approvers.config"));List<Approver> approvers = new ArrayList<>();for (int i = 1; i <= properties.size(); i++) {String className = properties.getProperty("approver" + i);Class<?> clazz = Class.forName(className);Approver approver = (Approver) clazz.newInstance();approvers.add(approver);}// 構建責任鏈for (int i = 0; i < approvers.size() - 1; i++) {approvers.get(i).setNextApprover(approvers.get(i + 1));}// 發送請求...}
}

(三)使用 Spring 實現責任鏈(依賴注入方式)

在 Spring 框架中,可以利用依賴注入來管理處理者實例,并通過 AOP 或自定義注解來簡化責任鏈的構建。

步驟如下

  1. 定義處理者接口和抽象類(同前文)。
  2. 將具體處理者聲明為 Spring Bean:

java

@Component
public class TeamLeaderApprover extends Approver {// ... 處理邏輯
}
  1. 使用@Autowired注入所有處理者,并按照順序構建鏈:

java

@Service
public class ApproveService {private List<Approver> approvers;@Autowiredpublic ApproveService(List<Approver> approvers) {// 假設approvers已按順序注入,可能需要通過@Order注解排序for (int i = 0; i < approvers.size() - 1; i++) {approvers.get(i).setNextApprover(approvers.get(i + 1));}}public void approve(LeaveRequest request) {approvers.get(0).processRequest(request); // 從鏈頭開始處理}
}

五、責任鏈模式與其他設計模式的對比

(一)vs 策略模式(Strategy Pattern)

對比維度責任鏈模式策略模式
目的處理請求鏈,一個請求可能被多個處理者處理封裝不同算法策略,選擇其中一種算法處理請求
結構處理者之間形成鏈式結構,請求沿鏈傳遞策略類之間是平行的,客戶端直接選擇具體策略
交互方式處理者自動傳遞請求,無需客戶端干預客戶端主動選擇具體策略并調用
適用場景請求需要按順序經過多個處理者處理需要動態切換不同算法實現

(二)vs 狀態模式(State Pattern)

對比維度責任鏈模式狀態模式
核心思想傳遞請求,多個處理者可能處理同一個請求根據對象狀態變化改變行為,狀態之間自動切換
處理者關系處理者之間是鏈式的,無狀態關聯狀態對象之間通常是互斥的,對象當前狀態決定行為
請求處理請求可能被多個處理者處理(取決于鏈的順序)請求由當前狀態對象處理,一個請求對應一個狀態處理

(三)vs 觀察者模式(Observer Pattern)

對比維度責任鏈模式觀察者模式
通信方向請求由發送者向處理者單向傳遞主題與觀察者之間是雙向通信(主題通知觀察者)
處理方式處理者按順序處理請求,通常只有一個處理者處理多個觀察者可以同時響應主題的變化
解耦程度發送者與處理者解耦,但處理者之間有鏈式關聯主題與觀察者解耦,觀察者之間無關聯

六、最佳實踐與注意事項

(一)鏈的長度控制

避免責任鏈過長,過長的鏈會導致請求處理效率低下,且調試困難。可以通過日志記錄鏈的處理過程,或者在鏈中設置最大處理深度限制。

(二)鏈尾處理

必須確保責任鏈有一個最終處理者(如默認處理者),避免請求未被處理的情況。例如在請假審批系統中,總經理作為鏈尾處理者,處理所有超過 7 天的請假請求。

(三)處理者的單一職責

每個處理者應專注于處理特定類型的請求,遵循單一職責原則,避免處理者承擔過多職責,導致邏輯復雜。

(四)日志與調試

在處理者中加入日志記錄,記錄請求的處理過程,方便調試和問題排查。例如記錄請求進入處理者的時間、處理結果、傳遞給下一個處理者的時間等。

(五)性能優化

如果責任鏈中的處理者較多,且大部分請求需要傳遞到鏈尾才能處理,可以考慮使用緩存或預處理機制,提前判斷請求應該由哪個處理者處理,避免逐個傳遞請求。

七、總結與拓展

責任鏈模式通過將處理者連成鏈條,實現了請求處理的解耦和靈活擴展,是處理多級流程、過濾鏈等場景的理想選擇。在 Java 開發中,我們可以通過抽象類和接口定義處理者,通過組合模式構建責任鏈,結合 Spring 等框架實現更高效的管理。

隨著微服務架構的普及,責任鏈模式的思想也被應用到分布式系統中,如請求攔截鏈、網關過濾器鏈等。深入理解責任鏈模式的核心原理,能夠幫助我們更好地設計可擴展的系統架構,應對復雜的業務需求變化。

在實際項目中,是否選擇責任鏈模式需要根據具體場景權衡,考慮請求處理的復雜度、處理者的動態性以及系統的可維護性等因素。通過合理應用責任鏈模式,我們可以構建出更加靈活、健壯的軟件系統。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/85595.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/85595.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/85595.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

如何進行頁面前端監控

&#x1f9d1;?&#x1f4bb; 寫在開頭 點贊 收藏 學會&#x1f923;&#x1f923;&#x1f923; 前端監控主要分三個方向 前端性能&#xff08;用戶體驗優化&#xff09; 異常監控 業務指標跟 下面我來分別介紹三類指標如何獲取 1&#xff09;前端性能指標&#xff1a; …

Ajax技術分析方法全解:從基礎到企業級實踐(2025最新版)

引言 Ajax技術自2005年正式命名以來,已支撐全球83%的Web應用實現異步交互。2025年最新數據顯示,單頁面應用(SPA)的Ajax請求密度已達日均120億次/應用。本文將系統化解析Ajax分析方法論,涵蓋從基礎原理到企業級工程實踐的完整技術棧。 一、Ajax技術架構解構 1.1 核心組件…

git管理github上的repository

1. 首先注冊github并創建一個倉庫&#xff0c;這個很簡單&#xff0c;網上教程也很多&#xff0c;就不展開說了 2. 安裝git&#xff0c;這個也很簡單&#xff0c;不過這里有個問題就是你當前windows的用戶名即&#xff1a;C/Users/xxx 這個路徑不要有中文&#xff0c;因為git …

Windows 下部署 SUNA 項目:虛擬環境嘗試與最終方案

#工作記錄 #回顧總結 本文記錄了在 Windows 系統上&#xff0c;通過 PyCharm 圖形界面&#xff08;盡量減少命令行操作&#xff09;部署 SUNA 項目時&#xff0c;針對不同虛擬環境方案的嘗試過程、遇到的問題以及最終選擇的可行方案&#xff0c;并補充了整體部署思路與推薦。…

無向圖的點、邊雙連通分量

文章目錄 點雙連通分量邊雙連通分量 有向圖的強連通分量&#xff1a;寒假學習筆記【匠心制作&#xff0c;圖文并茂】——1.20拓撲、強連通分量、縮點 點雙連通分量 在這之前&#xff0c;先讓我們了解幾個概念。 割點&#xff1a;刪除一個點和其連出的邊后&#xff0c;原圖會…

第六十二節:深度學習-加載 TensorFlow/PyTorch/Caffe 模型

在計算機視覺領域,OpenCV的DNN(深度神經網絡)模塊正逐漸成為輕量級模型部署的利器。本文將深入探討如何利用OpenCV加載和運行三大主流框架(TensorFlow、PyTorch、Caffe)訓練的模型,并提供完整的代碼實現和優化技巧。 一、OpenCV DNN模塊的核心優勢 OpenCV的DNN模塊自3.3…

Spring @Autowired自動裝配的實現機制

Spring Autowired自動裝配的實現機制 Autowired 注解實現原理詳解一、Autowired 注解定義二、Qualifier 注解輔助指定 Bean 名稱三、BeanFactory&#xff1a;按類型獲取 Bean四、注入邏輯實現五、小結 源碼見&#xff1a;mini-spring Autowired 注解實現原理詳解 Autowired 的…

勝牌?全球成為2026年FIFA世界杯?官方贊助商

勝牌全球將首次與國際足聯&#xff08;FIFA&#xff09;旗艦賽事建立合作關系。 此次贊助恰逢美國首個潤滑油品牌即將迎來160周年之際&#xff0c;其國際擴張步伐正在加快。 在這項全球頂級賽事籌備期間&#xff0c;勝牌全球將通過各種富有創意的零售和體驗活動與球迷互動。 …

YOLOV7改進之融合深淺下采樣模塊(DSD Module)和輕量特征融合模塊(LFI Module)

目錄 一、研究背景? 二. 核心創新點? ?2.1 避免高MAC操作? ?2.2 DSDM-LFIM主干網絡? 2.3 P2小目標檢測分支? ?3. 代碼復現指南? 環境配置 關鍵修改點 ?4. 實驗結果對比? 4.1 VisDrone數據集性能 4.2 邊緣設備部署 4.3 檢測效果可視化 ?5. 應用場景? …

【C/C++】chrono簡單使用場景

chrono使用場景舉例 1 輸出格式化字符串 示例代碼 auto now std::chrono::system_clock::now(); auto t std::chrono::system_clock::to_time_t(now); auto ms std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch()) % 1000;std::ostrin…

Med-R1論文閱讀理解-1

論文總結&#xff1a;Med-R1: Reinforcement Learning for Generalizable Medical Reasoning in Vision-Language Models 論文寫了什么&#xff1f; 本文提出了一種名為 Med-R1 的新框架&#xff0c;旨在通過強化學習&#xff08;Reinforcement Learning, RL&#xff09;提升…

京東熱點緩存探測系統JDhotkey架構剖析

熱點探測使用場景 MySQL 中被頻繁訪問的數據 &#xff0c;如熱門商品的主鍵 IdRedis 緩存中被密集訪問的 Key&#xff0c;如熱門商品的詳情需要 get goods$Id惡意攻擊或機器人爬蟲的請求信息&#xff0c;如特定標識的 userId、機器 IP頻繁被訪問的接口地址&#xff0c;如獲取用…

MCU_IO驅動LED

注意事項&#xff1a; 1、亮度要求較高的情況下&#xff0c;不能由IO直接驅動LED MCU_IO引腳輸出的電壓和電流較弱&#xff0c;如果對光的亮度有要求的話&#xff0c;需要使用三極管來驅動。 MCU_IO的電壓一般為3.3V或者5V&#xff0c;輸出電流一般10mA-25mA。 2、不同顏色…

MyBatis 深度解析:高效 Java 持久層框架實踐指南(基于 3.5.10)

一、MyBatis 核心架構與設計哲學 MyBatis 作為半自動 ORM 框架&#xff0c;核心設計目標是在靈活性與開發效率之間取得平衡。與 Hibernate 等全自動 ORM 框架不同&#xff0c;MyBatis 允許開發者完全控制 SQL 編寫&#xff0c;同時通過映射機制減少重復代碼&#xff0c;特別適…

二叉樹(二)

98.驗證二叉樹 中序遍歷二叉樹&#xff0c;每次遍歷存下當前節點的值&#xff0c;遍歷到下一個節點比較&#xff0c;根據二叉搜索樹的特性&#xff0c;左<中<右有&#xff1a; 如果當前值小于或等于上一個的值&#xff0c;說明不是二叉搜索樹 如果當前值大于上一個節點…

解決Vue3+uni-app導航欄高亮自動同步方案

路由跳轉自動識別導航高亮實現方法 以下代碼使用wd-tabbar組件實現路由跳轉時自動同步導航欄高亮狀態&#xff0c;適用于所有的Vue3uni-app項目。 請根據自身使用框架類型完成&#xff0c;也可根據我使用的UI組件進行完成地址如下&#xff1a; Tabbar 標簽欄 | Wot UI &#…

免費論文查重與AI檢測工具推薦

文章目錄 概要一、PaperPass二、PaperYY注意 概要 畢業季&#xff0c;總少不了查重這一步&#xff0c;甚至查 AI 率。推薦兩款免費查重AIGC檢測的工具。 論文免費查重查AI&#xff1a; https://paperpass.com/ https://www.paperyy.com/ 一、PaperPass 網址&#xff1a; ht…

4、ubuntu系統 | 文本和目錄操作函數

1、目錄操作函數 ls(列出目錄內容) 用途:列出指定目錄中的文件和子目錄。語法:ls [選項] [路徑]常用選項: -l:以長格式顯示文件詳細信息(權限、所有者、大小、時間等)。-a:顯示隱藏文件(以.開頭的文件)。-R:遞歸列出子目錄內容。# 列出當前目錄下的所有文件和子目…

C++--范圍for循環詳解

范圍 for 循環是 C11 引入的語法特性&#xff0c;用于簡化遍歷容器或數組元素的過程。它比傳統 for 循環更簡潔安全&#xff0c;特別適合初學者。以下是詳細講解&#xff1a; 基本語法 for (元素類型 變量名 : 容器/數組) {// 循環體&#xff08;使用變量名訪問當前元素&#…

RDMA簡介1之RDMA開發必要性

為了滿足大批量數據的采集、存儲與傳輸需求&#xff0c;越來越多的數據密集型應用如機器學習、雷達、金融風控、航空航天等選擇使用現場可編程邏輯門陣列作為數據采集前端硬件來實現高性能的數據采集系統。FPGA憑借其高靈活性、高并行能力及可高度定制化的特點&#xff0c;能夠…