Demo 地址: https://github.com/ooblee/HelloDesignPattern
1. 定义
命令模式(Command Pattern):将一个请求封装为一个对象,从而让我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。命令模式是一种对象行为型模式,其别名为动作(Action)模式或事务(Transaction)模式。
比如一个品牌下的多功能遥控器。可以控制风扇、空调、电灯、电视等等。
可以单独控制,也可以同时把所有电器打开。
也就是一个命令可能需要调用多个接收者,然后多个接收者又可以单独变化(比如换了台电视)。
这里的遥控器就类似于命令模式中的命令传递者。
我们可以很方便地发出指令,由遥控器来传达,控制相应设备。而不需要直接去和电器的控制进行交互。
命令模式抽象出一个层次,对请求的发送者和接受者进行解耦。
- 双方独立发展,无需互相依赖。
- 可以记录执行的命令,进行复原。
- 易于扩展新的命令。
2. 设计
主要角色
- 抽象命令(Command),定义命令执行方法。
- 具体命令(Concrete Command),调用接收者,执行命令的具体方法。
- 命令接收者(Receiver),命令的实际执行者,一个命令会存在一个或多个接收者。也可能一个接收者被多个命令使用。
- 命令传递者(Invoker),调用抽象命令,还可以记录执行的命令。
- 客户端(Client),发出命令。
类图如下:
抽象命令,一般只需要执行命令的方法。这里的例子支持回滚,所以这里加入了回滚方法
public interface ICommand {
void execute();
void fallback();
}
命令接收者 A
public class ReceiverA {
public void sayHello() {
System.out.println("A hello.");
}
public void sayHi() {
System.out.println("A hi.");
}
}
命令接收者 B
public class ReceiverB {
public void hello() {
System.out.println("B hello.");
}
}
具体命令 hello,依赖命令接收者 A 和 B 来执行 hello 命令
public class HelloCommand implements ICommand {
private ReceiverA receiverA = new ReceiverA();
private ReceiverB receiverB = new ReceiverB();
public void execute() {
receiverA.sayHello();
receiverB.hello();
}
public void fallback() {
System.out.println("hello fallback");
}
}
具体命令 hi,只有接收者 A 需要执行
public class HiCommand implements ICommand {
private ReceiverA receiverA = new ReceiverA();
public void execute() {
receiverA.sayHi();
}
public void fallback() {
System.out.println("hi fallback");
}
}
命令传递者,用来触发具体的命令,这里加了一个调用栈,记录执行过的命令。同时增加了回滚和复原所有操作的方法:
public class Invoker {
private Stack<ICommand> history = new Stack<ICommand>();
public Invoker() {
}
public void invoke(ICommand command) {
history.add(command);
command.execute();
}
/**
* 回滚
*/
public void fallback() {
if (history.size() > 0) {
ICommand command = history.pop();
// 回滚
command.fallback();
}
}
/**
* 复原
*/
public void restore() {
for (ICommand command : history) {
command.execute();
}
}
}
客户端使用姿势:
public class TestCommand {
public static void main(String[] args) {
// 接收者
Invoker invoker = new Invoker();
// 命令 hello
invoker.invoke(new HelloCommand());
// 命令 hi
invoker.invoke(new HiCommand());
// 再调用几次
System.out.println("\n反复调用:");
invoker.invoke(new HelloCommand());
invoker.invoke(new HelloCommand());
invoker.invoke(new HiCommand());
invoker.invoke(new HiCommand());
invoker.invoke(new HiCommand());
// 复原所有执行过的命令
System.out.println("\n复原所有命令:");
invoker.restore();
// 回滚
System.out.println("\n回滚:");
invoker.fallback();
// 回滚后查询记录
System.out.println("\n回滚后复原所有命令:");
invoker.restore();
}
}
输出如下:
A hello.
B hello.
A hi.
反复调用:
A hello.
B hello.
A hello.
B hello.
A hi.
A hi.
A hi.
复原所有命令:
A hello.
B hello.
A hi.
A hello.
B hello.
A hello.
B hello.
A hi.
A hi.
A hi.
回滚:
hi fallback
回滚后复原所有命令:
A hello.
B hello.
A hi.
A hello.
B hello.
A hello.
B hello.
A hi.
A hi.
2.1. 命令工厂
命令的创建,可以使用工厂模式。
2.2. 命令队列
当命令发起者不关心调用者何时执行,又或者需要中间设立缓冲区,可以创建命令队列。
2.3. 宏命令
命令和命令可以组合成新的命令。
3. 应用
应用场景:
- 调用者和接收者解耦。大家互不关心,独立变化。各管各的。
- 请求队列的实现。调用者先发起请求,但不关心接收者何时处理。
- 系统有撤销和恢复操作。
- 需要组合命令来实现宏命令(比如批处理)。
4. 特点
4.1. 优点
- 解耦。命令发起者和接收者不存在直接的引用,二者独立变化。
- 易扩展。增加新命令,不需要修改客户端代码。
- 易组合。可以通过命令与命令组合,创建更复杂的命令,比如一个批处理命令。
- 可以撤销和恢复。通过对命令的记录,可以撤销和恢复之前的命令。
4.2. 缺点
- 类膨胀。一个具体的命令要对应一个命令类,会导致具体命令类变多。