意圖(Intent)#
將請求的發送者與接收者解耦,讓多個物件都有機會處理該請求。將這些物件串成一條鏈,沿著鏈傳遞請求,直到某個物件處理它為止。
動機(Motivation)#
考慮一個 GUI 中的上下文相關說明(context-sensitive help)系統。使用者點擊任何介面元素都可以取得說明資訊,而提供的說明取決於被點擊的元素及其上下文——例如對話框中的按鈕可能與主視窗中的按鈕有不同的說明。
核心問題在於:發起說明請求的物件(按鈕)並不知道最終由誰提供說明。我們需要一種方式來解耦這兩者。
Chain of Responsibility 的做法是:
- 將請求沿著一條物件鏈傳遞
- 鏈上的第一個物件收到請求後,若能處理就處理,否則轉發給下一個物件
- 發出請求的物件無需知道誰會處理——請求擁有一個 implicit receiver
例如使用者在 PrintDialog 中的按鈕上點擊求助:請求從 aPrintButton 傳到 aPrintDialog,最終到達 anApplication 處理。客戶端完全不需要知道哪個物件最終回應了請求。
適用場景(Applicability)#
- 多個物件可能處理某請求,且處理者事先未知,需要自動判定
- 希望向多個物件之一發出請求,但不想明確指定接收者
- 可處理請求的物件集合需要在執行期間動態指定
結構(Structure)#
Handler 定義處理請求的介面,並持有指向下一個 handler(successor)的參考。ConcreteHandler 決定自己是否能處理該請求——能處理就處理,否則轉發給 successor。Client 將請求發送到鏈上的第一個 handler。
classDiagram
class Handler {
<<interface>>
+HandleRequest()
}
class ConcreteHandler1 {
+HandleRequest()
}
class ConcreteHandler2 {
+HandleRequest()
}
class Client
Handler <|.. ConcreteHandler1
Handler <|.. ConcreteHandler2
Handler o--> Handler : successor
Client --> Handler參與者(Participants)#
| 參與者 | 範例 | 職責 |
|---|---|---|
| Handler | HelpHandler | 定義處理請求的介面;(選擇性地)實作 successor 鏈結 |
| ConcreteHandler | PrintButton、PrintDialog | 處理自己負責的請求;可存取其 successor;若無法處理,則轉發給 successor |
| Client | - | 向鏈上的 ConcreteHandler 發起請求 |
協作方式(Collaborations)#
- Client 發出請求後,請求沿著鏈傳播,直到某個 ConcreteHandler 接手處理
鏈上的每個物件只需持有 一個 successor 參考,而非維護所有候選接收者的清單。這是此模式簡化物件間連結的關鍵。
sequenceDiagram
participant Client
participant Handler1
participant Handler2
participant Handler3
Client->>Handler1: HandleRequest()
Handler1->>Handler2: HandleRequest()
Handler2->>Handler3: HandleRequest()
Handler3-->>Handler2: (handled)優缺點(Consequences)#
優點:
- 降低耦合(Reduced coupling)——發送者與接收者彼此不需要明確知道對方的存在,物件之間只維護 successor 參考,大幅簡化物件互連
- 動態分配責任(Added flexibility)——可在執行期間透過增減或調整鏈上的物件來改變請求的處理方式,也可結合子類別化來靜態特化 handler
缺點:
- 不保證被處理(Receipt isn’t guaranteed)——請求可能傳到鏈的末端仍無人處理,特別是鏈的配置不當時
由於請求沒有明確的接收者,可能會在鏈的末端「落空」而無人處理。務必在設計時考慮預設處理機制或在鏈尾設置兜底 handler。
實作要點(Implementation)#
- Successor 鏈的實作方式
- 新建鏈結:在 Handler 中定義 successor 參考
- 利用既有鏈結:例如 part-whole 階層中的 parent 參考(如 Composite 模式中的父元件參考),可節省額外定義鏈結的成本
- 連接 successor——若無現成參考,Handler 通常同時維護介面與 successor 參考,並提供預設的轉發實作;子類別不感興趣的請求自然會被轉發
- 請求的表示方式
- 最簡單:硬編碼的操作呼叫(如
HandleHelp),安全但僅支援固定的請求集合 - 彈性方案:使用請求代碼(整數或字串)作為參數的單一 handler 函式,支援開放式請求集合,但犧牲型別安全
- 進階方案:使用 Request 物件 封裝請求參數,透過子類別定義不同參數,兼顧彈性與結構化
- 最簡單:硬編碼的操作呼叫(如
若語言支援(如 Smalltalk 的
doesNotUnderstand),可利用語言機制自動轉發未處理的訊息,省去手動實作轉發邏輯。
已知應用(Known Uses)#
- MacApp、ET++、Symantec TCL、NeXT AppKit 等 GUI 框架都使用此模式處理使用者事件,各自用不同名稱(EventHandler、Bureaucrat、Responder)但核心概念相同
- Unidraw 框架中的 Command 物件沿 Component 階層傳遞解釋,形成責任鏈
- ET++ 使用此模式處理圖形更新:
InvalidateRect操作沿容器物件鏈轉發,最終到 Window 層級處理
相關模式(Related Patterns)#
- Composite:常與 Chain of Responsibility 搭配使用,元件的 parent 可作為其 successor