《设计模式》— 行为型模式 — 命令模式

一、动机

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

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

Command 可以很容易地实现菜单,每一菜单中的选项都是一个菜单项类的实例。一个 Application 类创建这些菜单和他们的菜单项以及其余的用户界面。该 Application 类还跟踪用户已打开的 Document 对象。
在这里插入图片描述
该应用为每一个菜单项配置一个具体的 Command 子类的实例。当用户选择了一个菜单项时,该 MenuItem 调用它的 Command 对象的 execute 方法,而 execute 执行相应操作。MenuItem 对象并不知道它们使用的是 Command 的哪一个子类。 Command 子类里存放着请求的接受者,而 execute 操作将调用该接受者的一个或多个请求。

例如,PasteCommand 支持从剪贴板向一个文档粘贴正文。PasteCommand 的接受者是一个文档对象。该对象是实例化时提供的。execute 操作将调用该 Documentpaste 操作。

OpenCommandexecute 操作却有所不同:它提示用户输入一个文档名,创建一个相应的文档对象,将其放入作为接收者的应用对象中,并打开文档。

有时一个 MenuItem 需要执行一系列命令。那么我们可以定义一个 MacroCommand 类来执行任意给定的命令序列。该类实例没有明确的接受者,而序列中的命令各自定义其接受者。

这里我们需要注意命令模式是怎样解耦调用操作的对象和具有执行该操作所需信息的那个对象的。这使我们在设计用户界面时拥有很大的灵活性。一个应用如果想让一个菜单与一个按钮代表同一项功能,只需让它们共享相应具体 Command 子类的同一个实例即可。我们还可以动态地替换 Command 对象,这可用于实现上下文有关的菜单。我们也可以通过将几个命令组成更大的命令的形式来支持命令脚本。所有这些之所以成为可能,是因为提交一个请求的对象仅需要知道如何提交它,而不需要知道如何配置和执行该请求

二、适用性

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

三、结构

在这里插入图片描述

四、参与者

1、Command

声明执行操作的接口。

2、ConcreteCommand

  • 将一个接受者对象绑定于一个动作。
  • 调用接受者相应的操作,以实现 execute

3、Client

创建一个具体命令对象并设定它的接受者

4、Invoker

要求该命令执行这个请求

5、Receiver

知道如何实施与执行一个请求相关的操作。任何类都可能作为一个接受者。

五、协作

在这里插入图片描述

六、效果

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

七、实现

1、一个命令对象应达到何种智能程度

命令对象的能力可大可小。一个极端是它仅确定一个接受者和执行该请求的动作。另一个极端是它自己实现所有功能,根本不需要额外的接受者对象。当需要定义与已有的类无关的命令,或没有合适的接受者,或一个命令隐式地知道它的接受者时,可以使用后一个极端方式。例如,创建另一个应用窗口的命令对象本身可能和任何其他的对象一样有能力创建该窗口。在这两个极端间的情况是命令对象有足够的的信息可以动态地找到它们的接受者。

2、支持撤销和重做

为了支持这两个功能,ConcreteCommand 类可能需要存储额外的状态信息,包括:

  • 接受者对象,它真正执行处理该请求的各操作。
  • 接受者执行的操作的参数。
  • 如果处理请求的操作会改变接受者对象的那些值,那么这些值也必须先存储起来。接受这还必须提供一些操作,以使该命令可将接受者恢复到先前的状态。(但是这些状态又仅有接受者维护,因此可能需要接受者同样支持撤销操作)。

若应用只支持一次撤销操作,那么只需存储最近一次被执行的命令。而若要支持多级重做和撤销,就需要维护一个命令列表,该列表的最大长度决定了撤销和重做的结束。历史列表存储了已被执行的命令序列。向后遍历该列表并执行即执行多个撤销操作;向前遍历并执行则是多个重做操作。

有时可能不得不将一个可撤销的命令在它可以被放入历史列表之前先拷贝下来。这是因为执行原来的请求的命令对象将在稍后执行其他的请求。如果命令的状态在各次调用之间会发生变化,那么就必须进行拷贝以区分相同命令的不同调用(这种实现无形之间又会增加耦合性,这要求外部了解命令对象是否会发生变化)。

3、使用C++模板类

对不能被撤销和不需要参数的命令,可使用C++模板来实现,这样可以避免为每一种动作和接受者都创建一个 Command 子类。

八、应用

Qt中的信号槽机制是命令模式的一种应用。发出信号的对象不需要知道接受者是谁,也不需要知道该命令的执行过程和结果。而上层调用负责绑定这种信号和槽的关系。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值