大衛的Design Patterns學習筆記13:Chain of Responsibility

一、概述
Chain of Responsibility(職責鏈,以下簡稱CoR)模式通過將多個對象串接成一條鏈(Chain),並沿着這條鏈傳遞上層應用傳來的請求,直到有一個對象處理它爲止,使得多個對象都有機會處理上層應用傳來的請求,從而避免請求的發送者和接收者之間的耦合關係。對於Chain中的各個對象,可以採用類似單向鏈表或雙向鏈表的結構,保存各自後繼或者前接元素的引用/指針來實現鏈接(緊密鏈接),也可以僅僅由各對象的Container保存這種邏輯上的鏈接關係,而各對象彼此間並不需要知曉Chain中的其它對象(鬆散鏈接)。
對於Chain的結構,一個鏈可以是一條線,一個樹(普通的樹,或者平衡樹、紅黑樹等),也可以是一個環,在使用中可以根據需要選擇。

二、結構
以下是緊密鏈接CoR模式的典型結構:

1:CoR模式類圖示意
上述類圖中包括如下角色:
抽象處理者(Handler)角色:定義出一個處理請求的接口。如果需要,接口可以定義出一個方法,以設定和返回對下家的引用。這個角色通常由一個抽象類或接口實現。
具體處理者(ConcreteHandler)角色:具體處理者接到請求後,可以選擇將請求處理掉,或者將請求傳給下家。由於具體處理者持有對下家的引用,因此,如果需要,具體處理者可以訪問下家。

三、應用
在以下情況下可以考慮使用CoR模式:
1
、有多個的對象可以處理一個請求,哪個對象處理該請求運行時刻自動確定。
2
、你想在不明確指定接收者的情況下,向多個對象中的一個提交一個請求。
3
、可處理一個請求的對象集合應被動態指定。

四、優缺點
CoR模式使得發出這個請求的客戶端並不知道鏈上的哪一個對象最終處理這個請求,這使得系統可以在不影響客戶端的情況下動態地重新組織鏈和分配責任。從而可以很大程度地降低處理請求與處理對象,以及處理對象之間的耦合關係。

需要注意的時,CoR模式並不創建Responsibility Chain,Responsibility Chain的創建必須由系統的其它部分創建出來,即需要上層應用對Chain進行維護,同時,需要注意的是,在大多數情況下,Chain的構建應該遵循一定的規則,如由主到次,由特殊到普通(就好像在if..else if...else中,將較General的限制條件放在前面,可能使得較Rigorous的限制條件永遠得不到執行)。

五、舉例
MFC中的消息處理機制就是典型的CoR模式,它採用的是前面所說的鬆散鏈接的線狀Chain。下面是從jjhou的"MFC深入淺出"中拷貝下來的WM_COMMAND消息的處理流程圖:

由於作者要詳細討論內部的函數調用關係,所以有些內容是我們在討論CoR模式時不需要關注的。上圖對於不熟悉MFC的讀者可能有一些陌生,上圖中箭頭由右至左反映了類之間的繼承關係,對於討論CoR而言,若單從對象的角度考慮,只需要關注最右邊的幾個最終的Concrete類:CMyView、CMyDocument、CMyFrameWnd、CMyWinApp即可,當收到消息時,其處理工作由如下函數完成:
// in FRMWND.CPP(MFC 4.0)
BOOL CFrameWnd::OnCmdMsg(UINT nID, int nCode, void* pExtra,
AFX_CMDHANDLERINFO* pHandlerInfo)
{

    // pump through current view FIRST
    CView* pView = GetActiveView();
    if
 (pView != NULL && pView->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
        return
 TRUE;
        
    // then pump through frame
    if (CWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
        return
 TRUE;
        
    // last but not least, pump through app
    CWinApp* pApp = AfxGetApp();
    if
 (pApp != NULL && pApp->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
        return
 TRUE;
        
    return
 FALSE;
}


// in VIEWCORE.CPP(MFC 4.0)
BOOL CView::OnCmdMsg(UINT nID, int nCode, void* pExtra, AFX_CMDHANDLERINFO* pHandlerInfo)
{

    // first pump through pane
    if (CWnd::OnCmdMsg(nID, nCode, pExtra, pHandlerInfo))
        return
 TRUE;
    
    // then pump through document
    BOOL bHandled = FALSE;
    if
 (m_pDocument != NULL)
    {

        // special state for saving view before routing to document
        _AFX_THREAD_STATE* pThreadState = AfxGetThreadState();
        CView* pOldRoutingView = pThreadState->m_pRoutingView;
        pThreadState->m_pRoutingView = this;
        bHandled = m_pDocument->OnCmdMsg(nID, nCode, pExtra, pHandlerInfo);
        pThreadState->m_pRoutingView = pOldRoutingView;
    }

    
    return
 bHandled;
}

由此,可以大致看出消息在系統內流動的過程(注意,這是主窗口CMainFrame收到消息時的處理過程,其它對象收到消息時的處理過程類似,關於MFC消息處理的更深入的介紹,請參閱<深入淺出MFC>)。

雖然採用CoR進行消息處理的機制在MFC中根深蒂固地存在着(MFC採用消息映射表屏蔽了內部的複雜處理,DefWindowProc等輔助機制更進一步讓整個消息處理顯得自然而簡單),但採用CoR進行消息處理在現代軟件設計中往往被認爲是低效的,而且,在Java等更爲純粹的OOP語言中採用類似的機制很不可行,以下是典型的基於CoR的消息處理:

import java.applet.Applet;
import java.awt.*;

public class
 MouseSensor extends Frame {
   public static
 void main(String[] args) {
      MouseSensor ms = new MouseSensor();
      ms.setBounds(10,10,200,200);
      ms.show();
   }

   public
 MouseSensor() {
      setLayout(new BorderLayout());
      add(new MouseSensorCanvas(), "Center");
   }
}


class
 MouseSensorCanvas extends Canvas {
   public
 boolean mouseUp(Event event, int x, int y) {
      System.out.println("mouse up");
      return
 true; // Event has been handled. Do not propagate to container.
   }
   public
 boolean mouseDown(Event event, int x, int y) {
      System.out.println("mouse down");
      return
 true; // Event has been handled. Do not propagate to container.
   }
}


在Java中,採用CoR進行消息處理存在以下弊端:
1
、基於CoR的消息處理不可避免需要進行類的繼承和重載,如,爲了給按鈕添加鼠標移動處理,必須通過派生新的按鈕子類來完成,而類似這樣的需求過於平常,因此會使我們的應用中包含過多的類(MFC處理機制不會引起這個問題,因爲對於MFC程序員而言,我們總是說:““窗體的鼠標移動到按鈕上時的消息處理”,而不是“窗體上按鈕的鼠標移動消息處理”);
2
、消息處理與其它類的方法混雜在類的實現中,不便於管理;
3
、有失靈活性,不便於消息處理的動態添加、刪除(雖然可以通過添加flag來封閉消息處理邏輯,但相信沒有人會認爲這是一種好辦法)。
基於以上諸多原因,Java設計者爲了維持語言本身的簡單性,堅決地淘汰了AWT中舊的基於CoR的消息處理機制,在引入新的界面方案javax.swing的同時,引入了新的基於Observer模式的消息處理機制。

以下是新的基於Observer模式的消息處理:

import java.awt.*;
import java.awt.event.*;

public class
 MouseSensor extends Frame {
   public static
 void main(String[] args) {
      MouseSensor ms = new MouseSensor();
      ms.setBounds(10,10,200,200);
      ms.show();
   }

   public
 MouseSensor() {
      Canvas canvas = new Canvas();

      canvas.addMouseListener(new MouseAdapter() {
         public
 void mousePressed(MouseEvent e) {
            System.out.println("mouse down");
         }

         public
 void mouseReleased(MouseEvent e) {
            System.out.println("mouse up");
         }
      });


      setLayout(new BorderLayout());
      add(canvas, "Center");
   }
}


與基於CoR的消息處理相比,基於Observer的消息處理由於將消息處理交給了單獨的Observer來處理,使得程序結構更清晰,而且高效(消息直接被對應的Observer處理,無需輪詢)。

但這不表示基於CoR的消息處理沒有存在的價值,基於CoR的消息處理可以極大程度上降低消息的發送者與接收者之間的耦合程度,同時,在有些情況下,Observer模式並不能替代CoR模式進行消息處理,如:
1
、需要消息被多個接收者依次處理時,並且消息可能在處理的過程中被修改或者處理順序爲我們所關注時;
2
、當消息沒有經過嚴格分類時,應用Observer模式會變得比較困難。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章