命令模式是一种行为设计模式,可将一个请求封装为一个对象,用不同的请求将方法参数化,从而实现延迟请求执行或将其放入队列中或记录请求日志,以及支持可撤销操作。其别名为动作(Action)模式或事务(Transaction)模式。
Command is a behavior design pattern, which can encapsulate a request as an object, parameterize the method with
different requests, so as to delay the execution of the request or put it in the queue or record the request log,
and support revocable operations.
结构设计
命令模式包含如下角色:
Command,命令基类,声明一个执行命令的接口。
ConcreteCommand,具体命令类,实现各种类型的请求。具体命令自身并不完成工作,而是会将调用委派给一个业务逻辑对象(Receiver)。但为了简化代码,这些类可以进行合并。
Invoker,调用者,负责对请求进行初始化,其中必须包含一个成员变量来存储对于命令对象的引用。触发命令执行,但不向接收者直接发送请求。 注意,调用者并不负责创建命令对象:它通常会通过构造函数从客户端处获得预先生成的命令。
Receiver,接收者,定义业务逻辑。几乎任何对象都可以作为接收者。绝大部分命令只处理如何将请求传递到接收者的细节,接收者自己会完成实际的工作。简化代码,这些类可以与具体命令类进行合并。
Client,客户端,创建并配置具体命令对象。客户端必须将包括接收者实体在内的所有请求参数传递给命令的构造函数。此后,生成的命令就可以与一个或多个发送者相关联了。
命令模式类图表示如下:
伪代码实现
接下来将使用代码介绍下命令模式的实现。
// 1、命令基类,声明一个执行命令的接口
public interface ICommand {
void execute();
}
// 2、具体命令类,实现各种类型的请求
public class ConcreteCommandA implements ICommand {
private Receiver receiver;
public ConcreteCommandA(Receiver receiver) {
this.receiver = receiver;
}
@Override
public void execute() {
System.out.println("this is a concrete command A instance");
receiver.actionA();
}
}
public class ConcreteCommandB implements ICommand {
private Receiver receiver;
public ConcreteCommandB(Receiver receiver) {
this.receiver = receiver;
}
@Override
public void execute() {
System.out.println("this is a concrete command B instance");
receiver.actionB();
}
}
// 3、接收者,定义业务逻辑。绝大部分命令只处理如何将请求传递到接收者的细节,接收者自己会完成实际的工作。为简化代码,这些类可以与具体命令类进行合并
public class Receiver {
public void actionA() {
System.out.println("action A in a receiver instance");
}
public void actionB() {
System.out.println("action B in a receiver instance");
}
}
// 4、调用者,负责对请求进行初始化和触发命令执行。注意,调用者并不负责创建命令对象:它通常会通过构造函数从客户端处获得预先生成的命令
public class Invoker {
private ICommand command;
public Invoker(ICommand command) {
this.command = command;
}
public void executeCommand() {
this.command.execute();
}
}
// 5、客户端
public class CommandClient {
public void test() {
// (1) 创建接收者实例
Receiver receiver = new Receiver();
// (2) 创建命令实例
ICommand commandA = new ConcreteCommandA(receiver);
// (3) 创建调用者实例
Invoker invokerA = new Invoker(commandA);
// (4) 执行命令
invokerA.executeCommand();
ICommand commandB = new ConcreteCommandB(receiver);
Invoker invokerB = new Invoker(commandB);
invokerB.executeCommand();
}
}
适用场景
在以下情况下可以考虑使用命令模式:
(1) 如果需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互,可考虑使用该模式。
(2) 如果需要通过操作来参数化对象,可考虑使用该模式。命令模式可将特定的方法调用转化为独立对象。这一改变也带来了许多有趣的应用:开发者可以将命令作为方法的参数进行传递、将命令保存在其他对象中,或者在运行时切换已连接的命令等。
(3) 如果需要将操作放入队列中、操作的执行或者远程执行操作,可考虑使该模式。同其他对象一样, 命令也可以实现序列化 (序列化的意思是转化为字符串),从而能方便地写入文件或数据库中。一段时间后,该字符串可被恢复成为最初的命令对象。因此,可以延迟或计划命令的执行。 但其功能远不止如此。使用同样的方式,还可以将命令放入队列、 记录命令或者通过网络发送命令。
(4) 如果需要实现命令的撤销(Undo)操作和恢复(Redo)操作,可考虑使该模式。为了能够回滚操作, 需要实现已执行操作的历史记录功能。 命令历史记录是一种包含所有已执行命令对象及其相关程序状态备份的栈结构。
这种方法有两个缺点。 首先, 程序状态的保存功能并不容易实现, 因为部分状态可能是私有的。 可以使用备忘录模式来在一定程度上解决这个问题。
其次, 备份状态可能会占用大量内存。 因此, 有时需要借助另一种实现方式:命令无需恢复原始状态,而是执行反向操作。反向操作也有代价: 它可能会很难甚至是无法实现。
优缺点
命令模式有以下优点:
(1) 可以实现撤销和恢复功能。
(2) 可以实现操作的延迟执行。
(3) 可以将一组简单命令组合成一个复杂命令。
(4) 符合开闭原则。可以解耦触发和执行操作的类。
(5) 符合单一职责原则。发起操作和执行操作的类进行解耦。
但是该模式也存在以下缺点:
(1) 代码可能会变得更加复杂。使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个命令都需要设计一个具体命令类,因此某些系统可能需要大量具体命令类,这将影响命令模式的使用。
参考
《设计模式 可复用面向对象软件的基础》 Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides 著, 李英军, 马晓星等译
https://design-patterns.readthedocs.io/zh_CN/latest/behavioral_patterns/command.html 命令模式
https://refactoringguru.cn/design-patterns/command 命令模式
https://www.runoob.com/design-pattern/command-pattern.html 命令模式
https://www.cnblogs.com/adamjwh/p/10923122.html 简说设计模式——命令模式