1、意图
使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
2、动机
考虑一个图形用户界面中的上下文有关的帮助机制。用户在界面的任一部分上点击就可以得到帮助信息,所提供的帮助依赖于点击的是界面的那一部分以及其上下文。例如,对话框中的按钮的帮助信息就可能和主窗口中类似的按钮不同。如果对那一部分界面没有特定的帮助信息,那么帮助系统应该显示一个关于当前上下文的较一般的帮助信息 ----比如说,整个对话框。
因此很自然的,应根据普遍性(generality)即从最特殊到最普遍的顺序来组织帮助信息。而且,很明显,在这些用户界面对象中会有一个对象来处理帮助请求;至于是哪一个对象则取决于上下文以及可用的帮助具体到何种程度。
这儿的问题是提交帮助请求的对象(如按钮)并不明确知道谁是最终提供帮助的对象。我们要有一种办法将提交帮助请求的对象与可能提供帮助信息的对象解耦(decouple)。Chain of responsibility模式告诉我们应该怎么做。
这一模式的想法是,给多个对象处理一个请求的机会,从而解耦发送者和接受者。该请求沿对象链传递直至其中一个对象处理它,如下图所示。
从第一个对象开始,链中收到请求的对象要么亲自处理它,要么转发给链中的下一个候选者。提交请求的对象并不明确地知道哪一个对象将会处理它--我们说该请求有一个隐式的接受者(implicit receiver)。
假设用户在一个标有“Print”的按钮窗口组件上单击帮助,而该按钮包含在一个PrintDialog的实例中,该实例知道它所属的应用对象(见前面的对象框图)。下面的交互框图(diagram)说明了帮助请求怎样沿链传递:
在这个例子中,既不是aPrintButton也不是aPrintDialog处理该请求;它一直被传递给anApplication,anApplication处理它或忽略它。提交请求的客户不直接引用最终响应它的对象。
要沿链转发请求,并保证接受者为隐式的(implicit),每个在链上的对象都有一致的处理请求和访问链上后继者的接口。例如,帮助系统可定义一个带有相应的handleHelp操作的Helphandler类。Helphandler可为所有候选对象类的父类,或者它可被定义为一个混入(mixin)类。这样想处理帮助请求的类就可将Helphandler作为其一个父类,如下页上图所示。
按钮、对话框,和应用类都是用HelpHandler操作来处理帮助请求。HelpHandler的HandleHelp操作缺省的是将请求转发给后继。子类可重定义这一操作以在适当的情况下提供帮助;否则它们可使用缺省实现转发该请求。
3、适用性
在以下条件下使用responsibility链:
· 有多个的对象可以处理一个请求,哪个对象处理该请求运行时刻自动确定。
· 你想在不明确指定接受者的情况下,向多个对象中的一个提交一个请求。
· 可处理一个请求的对象集合应被动态指定。
4、结构
一个典型的对象结构可能如下图所示:
5、参与者
· Handler(如HelpHandler)
--定义一个处理请求的接口。
--(可选)实现后继链。
· ConcreteHandler(如PrintButton和PrintDialog)
-- 处理它所负责的请求。
-- 可访问它的后继者。
-- 如果可处理该请求,就处理之;否则该请求转发给它的后继者。
· Client
-- 向链上的具体处理者(ConcreteHandler)对象提交请求。
6、协作
当客户提交一个请求时,请求沿链传递直至有一个ConcreteHandler对象负责处理它。
7、效果
Responsibility链有下列优点和缺点(liabilities):
1)降低耦合度 该模式使得一个对象无需知道是其他哪一个对象处理其请求。对象仅需知道该请求会被“正确”地处理。接受者和发送者都没有对方的明确的信息,且链中的对象不需知道链的结构。
结果是,职责链可简化对象的相互连接。它们仅需保持一个指向其后继者的引用,而不需保持它所有的候选接受者的引用。
2)增强了给对象指派指责的灵活性 当在对象中分派职责时,职责链给你更多的灵活性你可以通过在运行时刻对该链进行动态的增加或修改来增加或改变处理一个请求的那些职责。你可以将这种机制与静态的特例化处理对象的继承机制结合起来使用。
3)不保证被接受 既然一个请求没有明确的接收者,那么就不能保证它一定会被处理 ----该请求可能一直到链的末端都得不到处理。一个请求也可能因该链没有被正确配置而得不到处理。
8、实现
下面是在职责链模式中要考虑的实现问题:
1)实现后继者链 有两种方法可以实现后继者链。
a) 定义新的链接(通常在Handler中定义,但也可由ConcreteHandlers来定义)。
b)使用已有的链接。
我们的例子中定义了新的链接,但你常常可使用已有的对象引用来形成后继者链。例如,在一个部分--整体层次结构中,父构件引用可定义一个部件的后继者。窗口组件(Widget)结构可能早已有这样的链接。Compsite更详细地讨论了父构件引用。
当已有的链接能够支持你所需的链时,完全可以使用它们。这样你不需要明确定义链接,而且可以节省空间。但如果该结构不能反应应用所需的职责链,那么你必须定义额外的链接。
2)连接后继者 如果没有已有的引用可定义一个链,那么你必须自己引入它们。这种情况下Handler不仅定义该请求的接口,通常也维护后继链接。这样Handler就提供了HandleRequest的缺省实现:HandleRequest向后继者(如果有的话)转发请求。如果Concretehandler子类对该请求不感兴趣,它不需重定义转发操作,因为它的缺省实现进行无条件的转发。
此处为一个HelpHandler基类,它维护一个后继者链接:
Class HelpHandler{
public:
HelpHandler(HelpHandler* s):_successor(s){}
virtual void HandleHelp();
private:
HelpHandler* _successor;
};
void HelpHandler::HandleHelp(){
if(_successor){
_successor->HandleHelp();
}
}
3)表示请求 可以有不同的方法表示请求。最简单的形式,比如在HandleHelp的例子中,请求是一个硬编码的(hard-coded)操作调用。这种形式方便而且安全,但你只能转发Handler类定义的固定的一组请求。
另一选择是使用一个处理函数,这个函数以一个请求码(如一个整形常数或一个字符串)为参数。这种方法支持请求数目不限。唯一的要求是发送发和接受方在请求如何编码问题上应达成一致。
这种方法更为灵活,但它需要用条件语句来区分请求代码以分派请求。另外,无法用类型安全的方法来传递请求参数,因此他们必须被手工打包和解包。显然,相对于直接调用一个操作来说它不太安全。
未解决参数传递问题,我们可使用独立的请求对象来封装请求参数。Request类可明确地描述请求,而新类型的请求可用它的子类来定义。这些子类可定义不同的请求参数。处理者必须知道请求的类型(即它们正使用哪一个Request子类)以访问这些参数。
为标识请求,Request可定义一个访问器(accessor)函数以返回该类的标识符。或者,如果实现语言支持的话,接受者可使用运行时的类型信息。
9、代码示例
。。。。。。