命令(Command)模式

命令模式是一种对象行为型模式,主要用于将请求封装为一个对象,以实现请求的参数化、队列化,支持撤销/重做操作。在系统中,它通过将请求的发起者与执行者解耦,提高了灵活性。适用于需要动态指定请求执行者或需要支持撤销/重做功能的场景。此模式包括Command接口、ConcreteCommand实现类、Invoker请求调用者和Receiver请求接收者四个角色。通过Command模式,可以实现回调函数的面向对象替代,支持在不同时间指定、排列和执行请求,以及构建事务等高级操作。
摘要由CSDN通过智能技术生成

命令(Command)模式

隶属类别——对象行为型模式


1. 意图

将一个请求封装成一个对象,以便使用不同的请求、队列或者日志来参数化(使用统一的语言去调用,例如遥控器插插槽根本不在乎所拥有的是什么命令,只要该命令对象实现了Command接口就可以了)其他对象。命令模式也支持可撤销操作。

2. 别名

动作(Action), 事务(Transaction)

3. 动机

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

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

命令模式通过将请求本身变成一个对象来使工具箱的对象可向未指定的应用对象提出请求。这个对象可被存储并向其他的对象一样被传递。这一模式的关键是一个抽象的Command类或者接口,它定义了一个执行操作的接口。 其最简单的形式是一个抽象的Execute操作。 具体的Command实现将真正的接受者(Receiver)作为其一个实例变量(使用委托的方式)。并实现Execute操作,指定接受者采取的动作。而接受者有执行该请求所需的具体信息。
在这里插入图片描述

该应用为每一个菜单项去实现Command接口,该MenuItem使用一个具体的接受者作为构造参数进行构造,且在对应的MenuItem中的Execute()中,采用委托的方式,方法内部调用该菜单命令所应该完成的操作。Execute()中可以可以调用一个或者多个接受者的操作。

有时候一个MenuItem需要执行一系列命令,例如,你需要“Party”MenuItem,你执行这一个命令是,就可以弄暗灯光,打开音响和电视,设置好DVD,并让热水器开始加温。因为这种需要将多条命令串联起来的情况很常见,我们有时候可以定义MenuItem让其成为一个MacroCommand(宏命令),MacroCommand是一个Command的一个实现,它执行了一个命令序列,MacroCommand没有明确的接受者,而序列中的命令都各自定义了其接受者:

在这里插入图片描述

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

4. 适用性

当你有如下需求时,可使用Command模式:

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

5. 结构

在结构类的UML图时,HeadFirst和GOF对于,客户和具体命令类之间的关系结果不一,HeadFirst表明的是associatio(关联),GOF表明的是(依赖),为此,我查询了一些资料:

相对而比较多认同的答案是这个:

In general, you use an association to represent something like a field in a class. The link is always there, in that you can always ask an order for its customer. It need not actually be a field, if you are modeling from a more interface perspective, it can just indicate the presence of a method that will return the order’s customer.

To quote from the 3rd edition of UML Distilled (now just out) “a dependency exists between two elements if changes to the definition of one element (the supplier) may cause changes to the other (the client)”. This is a very vague and general relationship, which is why the UML has a host of stereotypes for different forms of dependency. In code terms, such things as naming a parameter type and creating an object in a temporary variable imply a dependency.

在这里插入图片描述

6. 参与者

  • Command

    ——声明执行操作的接口

  • ConcreteCommand(LightOnCommand,DVDOnCommand,HottobOnCommand)

    ——将一个接受者对象绑定于一个动作

    ——调用接受者相应的操作,以实现execute()

  • Client(Application)

    ——创建一个具体命令对象并设定它的接受者,创建许多命令对象加载到Invoker中

  • Invoker(RemoteControl)

    ——管理一组命令对象,相当于每个按钮都有一个对象。每当按下按钮,就调用对应的按钮相应的命令。

  • Reveiver(Light,DVD,Hottob)

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

7. 协作

  • Client创建一个ConcreteCommand对象并制定它的Receiver对象

  • 某Invoker对象存储该ConcreteCommand对象。

  • 该Invoker通过调用Command对象的execute操作来提交一个请求。若该命令是可撤销的。ConcreteCommand就在执行Execute操作之前存储当前状态以用于取消该命令。

  • ConcreteCommand对象对调用它的Receiver的一些操作以执行该请求

    下图是展示了这些对象之间的交互的序列图。它说明了Command是如何将调用者和接受者(以及它执行的请求)解耦的:

在这里插入图片描述

8. 效果

Command模式有以下的优点和缺点:

优点:

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

缺点(其实差不多都是同一点):

  • 类的数目大大增加 完成一个目标需要大量的类和对象协同合作。应用程序开发人员需要小心正确地开发这些类。
  • 提升了实现和维护的工作量 每个单独的命令都是一个ConcreteCommand类,它增加了实现和维护的类的数量。

9. 实现

实现Command模式的时候须考虑以下问题:

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

  • 2.支持取消(undo)和重做(redo) 如果Command提供方法逆转(reverse)它们操作的执行(例如LightOnCommand的undo()),就可以支持取消和重做(两次撤销就相当于重做啊! 仅仅只存储一次操作时)功能。为达到这个目的ConcreteCommand类可能需要存储额外的状态信息。这个状态包括:

    • 接受者对象,它真正执行出该请求的各操作
    • 接受者上执行操作的参数。
    • 如果处理请求的操作会改变接受者对象中某些值,那么这些值也必须先存储起来。接受者还必须提供一些操作,以使该命令可将接受者恢复到它先前的状态

    若应用只支持一次取消操作,那么只需存储最近一次呗执行的命令。而若要支持多级的取消和重做,就需要有一个已被执行命令的历史列表(history list)——感觉可以用队列实现,该列表的最大长度决定了取消和重做的级数。历史列表存储了一倍执行的命令序列。向后遍历该列表并逆向执行(reverse-executing)命令是取消它们的结果,向前遍历并执行命令是重执行它们。

    有时可能不得不将一个可撤销的命令在它可以被放入历史列表中之前先拷贝下来。这是因为执行原来的请求的命令对象将在稍后执行其他的请求。如果命令的状态在各次调用之间会发生变化,那就必须进行拷贝以区分相同的命令的不同的调用。

    例如,一个删除选定对象的删除命令(DeleteCommand)在它每次被执行时,必须存储不同的对象集合。因此该删除命令对象在执行后必须被拷贝,并且将该拷贝放入历史列表中。如果该命令的状态在执行时从不改变,则不需要拷贝,而仅需将一个对该命令的引用放入历史列表。在放入历史李彪中之前必须要备考的那些Command起着原型(Proxytype模式)的作用。

  • 3.避免取消操作过程中的错误累积 在实现一个可靠的,能保持原先语义的取消/重做机制时,可能会遇到滞后影响问题,由于命令重复的执行、取消执行、和重执行的过程可能会积累错误,以至于一个应用的状态最终偏离初始值。这就有必要在Command中存入更多的信息以保证这些对象可被精确地复原成它们初始的状态。这里可以使用Memento(备忘录)模式来让该Command访问这些信息而不暴露其它对象的内部信息。

  • 4.使用C++模板(在Java相当于使用什么呢?又是这个问题。。两次了还没解决,妈耶!) 对于

    • 1.不能被取消

    • 2.不需要参数的命令

      我们可以使用C++模板来实现,这样可以避免为每一个动作和接受者都创建一个Command子类。

  • 5.设置空命令 对于部分Invoker进行初始化时,可以采用空命令,从而确保每个插槽一定都是有引用存在的。空命令的实现如下

    public class NoCommand implements Command{
         
    	
    	@Override
    	public void execute() {
         }
    	
    	@Override
    	public void undo() {
         }
    }
    
    

    NoCommand是一个空对象的(null object)的例子。当你不向返回一个有意义的对象时,空对象也很有用,客户也可以将处理null的责任转移给空对象。举例来说,遥控器不可能一出厂就设置了有意义的命令对象,所以提供了NoCommand对象作为代用品,当调用它的execute()方法时,这种对象就什么事情都不做。

    在许多设计模式中,都会看到空对象的使用。甚至有些时候,空对象本省也被视为一种设计模式。

10. 代码示例

首先是Command——Command.java

public interface Command {
   
	void execute();
	void undo();
}

然后是ConcreteCommand——LightOnCommand & LightOffCommand & CeilingFanHighCommand & CeilingFanLowCommand & CeilingFanMediumCommand & CeilingFanOffCommand & NoCommand

LightOnCommand.java

public class LightOnCommand implements Command{
   
	Light light;
	
	public LightOnCommand(Light light) {
   
		this.light = light;
	}
	
	@Override
	public void execute() {
   
		light.on();
	}
	
	
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值