5.2-Command「Action,Transaction」-命令模式-动作模式-事务模式-行为型模式

目的

将请求封装为对象,从而可用不同的请求:

  • 对客户行为参数化
  • 对请求排队或记录日志
  • 支持可撤销的操作

示例

有时必须向某对象提交请求,但并不知道关于被请求的操作或请求的接收者的任何信息。

例如,用户界面工具箱包括按钮和菜单这样的对象,它们执行请求响应用户输入。但工具箱不能显式地在按钮或菜单中实现该请求,因为只有使用工具箱的应用知道该由哪个对象做哪个操作。

命令模式通过将请求本身变成一个可存储对象来使工具箱对象可向未指定的应用对象提出请求。

  • 关键是一个抽象的 Command 类,它定义了一个执行操作的接口。
  • Command 子类将接收者作为它的一个实例变量,并实现 Execute 操作

例如,PasteCommand 支持从剪贴板向一个文档(document)粘贴正文。 PasteCommand 的接收者是一个文档对象,该对象是实例化时提供的。Execute 操作将调用该 Document 的 Paste 操作。

MacroCommand:有时一个操作需要多个子操作完成,因此可定义 MacroCommand 执行一个命令序列

  • MacroCommand 没有明确的接收者,而序列中的命令各自定义其接收者。

优势:灵活。因为提交一个请求的对象仅需要知道如何提交它,而不需要知道该请求将会被如何执行

  • 一个应用如果想让一个菜单与一个按钮代表同一项功能,只需让它们共享相应具体 Command 子类的同一个实例即可
  • 可以动态地替换 Command 对象,这可用于实现上下文有关的菜单
  • 可通过将几个命令组成更大的命令的形式来支持命令脚本

适用性

  • 抽象出待执行的动作以参数化某对象(Command 模式是回调机制的一个面向对象的替代品)
  • 在不同的时刻指定、排列和执行请求。一个 Command 对象可以有一个与初始请求无关的生存期。
  • 支持取消操作:Command 的 Excute 操作可在实施操作前将状态存储起来,在取消操作时这个状态用来消除该操作的影响。
  • 支持修改日志:当系统崩溃后,可从磁盘中重新读入记录下来的命令并用 Execute 操作重新执行它们。(Command 接口中需添加装载操作和存储操作)
  • 用构建在原语操作上的高层操作构造一个系统:在支持事务(transaction)的信息系统中,一个事务封装了对数据的一组变动。
    • Command 模式提供了对事务进行建模的方法。

结构

  • Command:操作的接口
  • ConcreteCommand:将一个接收者对象绑定于一个动作,调用接收者相应的操作,以实现 Execute
  • Invoker(示例中的MenuItem):要求该命令执行这个请求
  • Receiver(示例中的 Document):知道如何实施与执行一个请求相关的操作

工作流程

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

优缺点

  • 将调用操作的对象与知道如何实现该操作的对象解耦。
  • 可将多个命令装配成一个组合命令(一般说来,组合命令是 Composite 模式的一个实例)
  • 增加新的 Command 很容易,因为这无须改变已有的类

实现

  • Command 的能力大小:
    • 一个极端是它仅确定一个接收者和执行该请求的动作。
    • 另一个极端是它自己实现所有功能,根本不需要额外的接收者对象:当需要定义与已有的类无关的命令,或没有合适的接收者,或一个命令隐式地知道它的接收者时使用
    • 在这两个极端间的情况是命令对象有足够的信息可以动态地找到它们的接收者。
  • 支持撤销(undo)和重做(redo)
    • Command 需要提供 Undo 操作
    • ConcreteCommand 类可能需要存储额外的状态信息,包括:
      • 接收者对象,它真正执行处理该请求的各操作。
      • 接收者执行的操作的参数。
      • 如果处理请求的操作会改变接收者对象中的某些值,那么这些值也必须先存储起来。
      • 接收者还必须提供一些操作,以使该命令可将接收者恢复到它先前的状态。
    • 命令列表:若要支持多级的撤销和重做,就需要有一个已被执行命令的历史列表
      • 向后遍历该列表并逆向执行(reverse-executing)命令是撤销它们的结果
      • 向前遍历并执行命令是重执行它们。
      • 拷贝命令:有时可能不得不将一个可撤销的命令在它可以被放入历史列表之前先拷贝下来:如果命令的状态在各次调用之间会发生变化,那就必须进行拷贝以区分相同命令的不同调用,反之则无需拷贝,存储引用即可。
        • 例如,一个删除选定对象的删除命令(DeleteCommand)在它每次被执行时,必须存储不同的对象集合。因此该删除命令对象在执行后必须被拷贝,并且将该拷贝放入历史列表中
    • 避免撤销操作过程中的错误积累:由于命令的重复执行、取消执行和重执行的过程中可能会积累错误,以致一个应用的状态最终偏离初始值。这就有必要在 Command 中存入更多的信息,以保证这些对象可被精确地复原成它们的初始状态。
  • 使用 C++模板:对不能被撤销和不需要参数的命令,可使用 C++模板来实现,这样可以避免为每一种动作和接收者都创建一个 Command 子类

相关模式

  • Composite(4.3)可用来实现宏命令。
  • Memento(5.6)可用来保持某个状态,命令用这一状态来取消它的效果。
  • 在被放入历史列表前必须被拷贝的命令起到一种原型(参见 Prototype(3.4))的作用。