意圖(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)#

參與者範例職責
HandlerHelpHandler定義處理請求的介面;(選擇性地)實作 successor 鏈結
ConcreteHandlerPrintButton、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)#

  • MacAppET++Symantec TCLNeXT AppKit 等 GUI 框架都使用此模式處理使用者事件,各自用不同名稱(EventHandler、Bureaucrat、Responder)但核心概念相同
  • Unidraw 框架中的 Command 物件沿 Component 階層傳遞解釋,形成責任鏈
  • ET++ 使用此模式處理圖形更新:InvalidateRect 操作沿容器物件鏈轉發,最終到 Window 層級處理

相關模式(Related Patterns)#

  • Composite:常與 Chain of Responsibility 搭配使用,元件的 parent 可作為其 successor