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