一、概述
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模式会变得比较困难。