Skip to content

行为型模式

引言

行为型模式涉及算法和对象间职责的分配。行为型模式不仅描述对象或类的模式,还描述它们之间的通信模式。这些模式刻画了在运行时难以跟踪的复杂的控制流。它们将你的注意力从控制流转移到对象间的联系方式上来。

类行为型模式使用继承机制在类间分派行为。本章包括两个这样的模式。其中 Template Method(5.10)较为简单和常用。模板方法是一个算法的抽象定义,它逐步地定义该算法,每一步调用一个抽象操作或一个原语操作,子类定义抽象操作以具体实现该算法。另一种类行为型模式是 Interpreter(5.3)。它将一个文法表示为一个类层次,并实现一个解释器作为这些类的实例上的一个操作。

对象行为型模式使用对象组合而不是继承。一些对象行为型模式描述了一组对等的对象怎样相互协作以完“成其中任一个对象都无法单独完成的任务。这里一个重要的问题是对等的对象如何互相了解对方。对等对象可以保持显式的对对方的引用,但那会增加它们的耦合度。在极端情况下,每一个对象都要了解所有其他的对象。Mediator(5.5)在对等对象间引入一个 mediator 对象以避免这种情况的出现。mediator 提供了松耦合所需的间接性。

Chain of Responsibility(5.1)提供更松的耦合。它让你通过一条候选对象链隐式地向一个对象发送请求。根据运行时情况任一候选者都可以响应相应的请求。候选者的数目是任意的,你可以在运行时决定哪些候选者参与到链中。

Observer(5.7)模式定义并保持对象间的依赖关系。典型的 Observer 的例子是 Smalltalk 中的模型/视图/控制器,其中一旦模型的状态发生变化,模型的所有视图都会得到通知。

其他的对象行为型模式常将行为封装在一个对象中并将请求指派给它。Strategy(5.9)模式将算法封装在对象中,这样可以方便地指定和改变一个对象所使用的算法。Command(5.2)模式将请求封装在对象中,这样它就可作为参数来传递,也可以被存储在历史列表里,或者以其他方式使用。State(5.8)模式封装一个对象的状态,使得当这个对象的状态对象变化时,该对象可改变它的行为。Visitor(5.11)封装分布于多个类之间的行为,而 Iterator(5.4)则抽象了访问和遍历一个集合中的对象的方式。

Chain of Responsibility(职责链)——对象行为型模式

意图

使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

动机

考虑一个图形用户界面中的上下文有关的帮助机制。用户在界面的任一部分上点击就可以得到帮助信息,所提供的帮助依赖于点击的是界面的哪一部分及其上下文。例如,对话框中按钮的帮助信息就可能和主窗口中类似的按钮不同。如果对那一部分界面没有特定的帮助信息,那么帮助系统应该显示一个关于当前上下文的较一般的帮助信息——比如说,整个对话框。

因此,很自然地,应根据普遍性(generality)即从最特殊到最普遍的顺序来组织帮助信息。而且,很明显,在这些用户界面对象中会有一个对象来处理帮助请求,至于是哪一个对象则取决于上下文以及可用的帮助具体到何种程度。

这里的问题是提交帮助请求的对象(如按钮)并不明确知道谁是最终提供帮助的对象。我们要有一种办法将提交帮助请求的对象与可能提供帮助信息的对象解耦(decouple)。Chain of Responsibility 模式告诉我们应该怎么做。

这一模式的想法是,给多个对象处理一个请求的机会,从而解耦发送者和接收者。该请求沿对象链传递直至其中一个对象处理它,从第一个对象开始,链中收到请求的对象要么亲自处理它,要么转发给链中的下一个候选者。提交请求的对象并不明确地知道哪一个对象将会处理它——我们说该请求有一个隐式的接收者(implicit receiver)。要沿链转发请求,并保证接收者为隐式的(implicit),每个在链上的对象都有一致的处理请求和访问链上后继者的接口。

适用性

  • 有多个对象可以处理一个请求,哪个对象处理该请求运行时自动确定。
  • 你想在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
  • 可处理一个请求的对象集合应被动态指定。

参与者

  • Handler
    • 定义一个处理请求的接口。
    • (可选)实现后继链。
  • ConcreteHandler
    • 处理它所负责的请求。
    • 可访问它的后继者。
    • 如果可处理该请求,就处理之;否则将该请求转发给它的后继者。
  • Client: 向链上的具体处理者(ConcreteHandler)对象提交请求。

协作

当客户提交一个请求时,请求沿链传递直至有一个 ConcreteHandler 对象负责处理它。

效果

  1. 降低耦合度 该模式使得一个对象无须知道是其他哪一个对象处理其请求。对象仅需要知道该请求会被“正确”地处理。接收者和发送者都没有对方的明确信息,且链中的对象不需要知道链的结构。结果是,职责链可简化对象的相互连接。它们仅需要保持一个指向其后继者的引用,而不需要保持它所有的候选接收者的引用。
  2. 增强了给对象指派职责的灵活性 当在对象中分派职责时,职责链给你更多的灵活性。你可以通过在运行时对该链进行动态的增加或修改来增加或改变处理一个请求的那些职责。你可以将这种机制与静态的特例化处理对象的继承机制结合起来使用。
  3. 不保证被接受 既然一个请求没有明确的接收者,那么就不能保证它一定会被处理——该请求可能一直到链的末端都得不到处理。一个请求也可能因该链没有被正确配置而得不到处理。

实现

  1. 实现后继者链
    • 定义新的链接(通常在 Handler 中定义,但也可由 ConcreteHandler 来定义)。
    • 使用已有的链接。
  2. 连接后继者 如果没有已有的引用可定义一个链,那么你必须自己引入它们。这种情况下 Handler 不仅定义该请求的接口,通常也维护后继者。这样 Handler 就提供了 HandleRequest 的默认实现:HandleRequest 向后继者(如果有的话)转发请求。如果 ConcreteHandler 子类对该请求不感兴趣,它不需要重定义转发操作,因为它的默认实现进行无条件的转发。
  3. 表示请求 可以有不同的方法表示请求。
    • 最简单的形式,比如在 HandleHelp 的例子中,请求是一个硬编码的(hard-coded)操作调用。这种形式方便而且安全,但你只能转发 Handler 类定义的固定的一组请求。
    • 另一选择是使用一个处理函数,这个函数以一个请求码(如一个整型常数或一个字符串)为参数。这种方法支持请求数目不限。唯一的要求是发送方和接收方在请求如何编码问题上应达成一致。这种方法更为灵活,但它需要用条件语句来区分请求代码以分派请求。另外,无法用类型安全的方法来传递请求参数,因此它们必须被手工打包和解包。显然,相对于直接调用一个操作来说它不太安全。
    • 为解决参数传递问题,我们可使用独立的请求对象来封装请求参数。Request 类可明确地描述请求,而新类型的请求可用它的子类来定义。这些子类可定义不同的请求参数。处理者必须知道请求的类型(即它们正使用哪一个 Request 子类)以访问这些参数。
    • 为标识请求,Request 可定义一个访问器(accessor)函数以返回该类的标识符。或者,如果实现语言支持的话,接收者可使用运行时的类型信息。

相关模式

职责链常与 Composite(4.3)一起使用。这种情况下,一个构件的父构件可作为它的后继。

Command(命令)——对象行为型模式

意图

将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。

别名

动作(action),事务(transaction)。

动机

有时必须向某对象提交请求,但并不知道关于被请求的操作或请求的接收者的任何信息。例如,用户界面工具箱包括按钮和菜单这样的对象,它们执行请求响应用户输入。但工具箱不能显式地在按钮或菜单中实现该请求,因为只有使用工具箱的应用知道该由哪个对象做哪个操作。而工具箱的设计者无法知道请求的接收者或执行的操作。

命令模式通过将请求本身变成一个对象来使工具箱对象可向未指定的应用对象提出请求。这个对象可被存储并像其他对象一样被传递。这一模式的关键是一个抽象的 Command 类,它定义了一个执行操作的接口。其最简单的形式是一个抽象的 Execute 操作。具体的 Command 子类将接收者作为它的一个实例变量,并实现 Execute 操作,指定接收者采取的动作。而接收者有执行该请求所需的具体信息。

适用性

  • 抽象出待执行的动作以参数化某对象。你可用过程语言中的回调(callback)函数表达这种参数化机制。所谓回调函数是指函数先在某处注册,而它将在稍后某个需要的时候被调用。Command 模式是回调机制的一个面向对象的替代品。
  • 在不同的时刻指定、排列和执行请求。一个 Command 对象可以有一个与初始请求无关的生存期。如果一个请求的接收者可用一种与地址空间无关的方式表达,那么就可将负责该请求的命令对象传送给另一个不同的进程并在那里实现该请求。
  • 支持取消操作。Command 的 Excute 操作可在实施操作前将状态存储起来,在取消操作时这个状态用来消除该操作的影响。Command 接口必须添加一个 Unexecute 操作“,该操作取消上一次 Execute 调用的效果。执行的命令被存储在一个历史列表中。可通过向后和向前遍历这一列表并分别调用 Unexecute 和 Execute 来实现重数不限的“撤销”和“重做”。
  • 支持修改日志,这样当系统崩溃时,这些修改可以被重做一遍。在 Command 接口中添加装载操作和存储操作,可以用来保持一个一致的修改日志。从崩溃中恢复的过程包括从磁盘中重新读入记录下来的命令并用 Execute 操作重新执行它们。
  • 用构建在原语操作上的高层操作构造一个系统。这样一种结构在支持事务(transaction)的信息系统中很常见。一个事务封装了对数据的一组变动。Command 模式提供了对事务进行建模的方法。Command 有一个公共的接口,使得你可以用同一种方式调用所有的事务。同时使用该模式也易于添加新事务以扩展系统。

参与者

  • Command: 声明执行操作的接口。
  • ConcreteCommand:
    • 将一个接收者对象绑定于一个动作。
    • 调用接收者相应的操作,以实现 Execute。
  • Client:创建一个具体命令对象并设定它的接收者。
  • Invoker: 要求该命令执行这个请求。
  • Receiver: 知道如何实施与执行一个请求相关的操作。任何类都可能作为一个接收者。

协作

  • Client 创建一个 ConcreteCommand 对象并指定它的 Receiver 对象。
  • 某 Invoker 对象存储该 ConcreteCommand 对象。
  • 该 Invoker 通过调用 Command 对象的 Execute 操作来提交一个请求。若该命令是可撤销的,ConcreteCommand 就在执行 Excute 操作之前存储当前状态以用于取消该命令。
  • ConcreteCommand 对象调用它的 Receiver 的一些操作以执行该请求。

效果

  • Command 模式将调用操作的对象与知道如何实现该操作的对象解耦。
  • Command 是头等的对象。它们可像其他的对象一样被操纵和扩展。
  • 你可将多个命令装配成一个组合命令。一般说来,组合命令是 Composite 模式的一个实例。
  • 增加新的 Command 很容易,因为这无须改变已有的类。

备案号:闽ICP备2024028309号-1