文章

23种设计模式-责任链模式

责任链模式/chain of responsibility

Intent/目的

通过给予多个对象机会来处理请求,避免请求的发送者与接收者之间的耦合。将接收对象进行链式连接,并沿着链传递请求,直到有一个对象处理它。

Motivation/动机

考虑一个图形用户界面的上下文敏感帮助设施。用户可以通过单击界面的任何部分来获取帮助信息。提供的帮助取决于被选中的界面部分及其上下文;例如,对话框中的按钮小部件可能与主窗口中的类似按钮有不同的帮助信息。如果该界面部分没有具体的帮助信息,那么帮助系统应该显示一个关于即时上下文的更通用的帮助信息——例如,整个对话框。

因此,根据帮助信息的通用性来组织帮助信息是很自然的——从最具体到最通用。此外,很明显,帮助请求由几个用户界面对象之一处理;具体由哪一个处理取决于上下文以及可用帮助的具体程度。

这里的问题在于,最终提供帮助的对象对于发起帮助请求的对象(例如,按钮)并不是明确知道的。我们需要的是一种方法来解耦发起帮助请求的按钮和可能提供帮助信息的对象。责任链模式定义了这一过程是如何发生的。

这种模式的思想是通过给多个对象机会处理一个请求来解耦发送者和接收者。请求沿着一系列对象传递,直到其中一个对象处理它。

链上的第一个对象接收请求,要么处理它,要么将其转发给链上的下一个候选对象,后者也采取同样的操作。发出请求的对象并不明确知道谁将处理它——我们说请求有一个隐式接收者(implicit receiver).

假设用户点击标有“打印”的按钮小部件以获取帮助。该按钮包含在一个PrintDialog实例中,这个实例知道它所属的应用程序对象(参见前面的对象图)。以下交互图说明了帮助请求是如何沿着链转发的:

在这种情况下,aPrintButtonaPrintDialog都不处理请求;它在anApplication处停止了,anApplication可以处理它或忽略它。发出请求的客户端对最终满足请求的对象没有直接引用。

为了沿着链转发请求,并确保接收者保持隐式,链上的每个对象共享一个用于处理请求和访问其在链上的后继者的公共接口。例如,帮助系统可能定义一个带有相应HandleHelp操作的HelpHandler类。HelpHandler可以是候选对象类的父类,或者可以定义为一个mixin类。然后,希望处理帮助请求的类可以将HelpHandler作为父类:

ButtonDialogApplication类使用HelpHandler操作来处理帮助请求。HelpHandlerHandleHelp操作默认将请求转发给后继者。子类可以覆盖这个操作,在适当的情况下提供帮助;否则,它们可以使用默认实现来转发请求。

Applicability/应用场景

  • 可能有多个对象处理一个请求,并且处理者事先并不知道。处理者应该被自动确定。
  • 你想向几个对象中的一个发出请求,而不显式指定接收者
  • 可以处理请求的对象集应该被动态指定。

Structure/结构

Participants/角色

  • Handler (HelpHandler)
    • 定义处理请求的接口
    • (可选)实现后继链条
  • ConcreteHandler (PrintButton, PrintDialog)
    • 处理自己支持的请求
    • 可以访问它的后继者
    • 如果ConcreteHandler能够处理请求,它就会这样做;否则,它将请求转发给它的后继者。
  • Client
    • 向链上的一个ConcreteHandler对象发起请求。

Consequences/总结

1、 减少耦合。该模式使对象不必知道是哪个其他对象处理了一个请求。一个对象只需要知道请求将被“适当”处理。接收者和发送者都不需要明确了解对方,链中的一个对象也不需要了解链的结构。

因此,责任链可以简化对象间的连接。与其让对象维持对所有候选接收者的引用,不如保持对其后继者的单一引用。

2、在分配对象职责时增加了灵活性。责任链为在对象之间分配职责提供了额外的灵活性。通过在运行时向链中添加或以其他方式改变链,你可以增加或改变处理请求的职责。你还可以结合子类化来静态地专门处理处理程序。

3、 请求的处理不能保证。由于请求没有明确的接收者,不能保证它会被处理——请求可能会在链的末端消失而从未被处理。当链没有正确配置时,请求也可能不被处理。

责任链经常与组合模式(Composite)结合使用。在这种情况下,组件的父对象可以充当它的后继者。

本文由作者按照 CC BY 4.0 进行授权

© Poul.Y. 保留部分权利。