命令模式

是什么

命令模式允许将请求封装成一个对象(命令对象,包含执行操作所需的所有信息),并将命令对象按照一定的顺序存储在队列中,然后再逐一调用执行,这些命令也可以支持反向操作,进行撤销和重做。

这样发送者只需要触发命令就可以完成操作,不需要知道接受者的具体操作,从而实现两者间的解耦。

基本结构

  • 命令接口Command:接口或者抽象类,定义执行操作的接口。
  • 具体命令类ConcreteCommand: 实现命令接口,执行具体操作,在调用execute方法时使“接收者对象”根据命令完成具体的任务,比如遥控器中的“开机”,“关机”命令。
  • 接收者类Receiver: 接受并执行命令的对象,可以是任何对象,遥控器可以控制空调,也可以控制电视机,电视机和空调负责执行具体操作,是接收者。
  • 调用者类Invoker: 发起请求的对象,有一个将命令作为参数传递的方法。它不关心命令的具体实现,只负责调用命令对象的 execute() 方法来传递请求,在本例中,控制遥控器的“人”就是调用者。

简易实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
// 定义执行操作的接口,包含一个execute方法
public interface Command {
void execute();
}

// 实现命令接口,执行具体的操作
public class ConcreteCommand implements Command {
// 接收者对象
private Receiver receiver;

public ConcreteCommand(Receiver receiver) {
this.receiver = receiver;
}

@Override
public void execute() {
// 调用接收者相应的操作
receiver.action();
}
}

// 定义接受者类,知道如何实施和执行一个请求相关的操作
public class Receiver {
public void action() {
// 执行操作
}
}

// 定义具体调用者,调用命令对象执行请求
public class Invoker {
private Command command;

public Invoker(Command command) {
this.command = command;
}

public void executeCommand() {
command.execute();
}
}

// 调用者类中可以维护一个命令队列或者“撤销栈”,以支持批处理和撤销命令。
// 调用者类:命令队列和撤销请求
class Invoker {
private Queue<Command> commandQueue; // 命令队列
private Stack<Command> undoStack; // 撤销栈

public Invoker() {
this.commandQueue = new LinkedList<>();
this.undoStack = new Stack<>();
}

// 设置命令并执行
public void setAndExecuteCommand(Command command) {
command.execute();
commandQueue.offer(command);
undoStack.push(command);
}

// 撤销上一个命令
public void undoLastCommand() {
if (!undoStack.isEmpty()) {
Command lastCommand = undoStack.pop();
lastCommand.undo(); // 需要命令类实现 undo 方法
commandQueue.remove(lastCommand);
} else {
System.out.println("No command to undo.");
}
}

// 执行命令队列中的所有命令
public void executeCommandsInQueue() {
for (Command command : commandQueue) {
command.execute();
}
}
}

public class Main {
public static void main(String[] args) {
Receiver receiver = new Receiver();
Command command = new ConcreteCommand(receiver);
Invoker invoker = new Invoker(command);

invoker.executeCommand();
}
}

优缺点

命令模式在需要将请求封装成对象、支持撤销和重做、设计命令队列等情况下,都是一个有效的设计模式。

  • 撤销操作: 需要支持撤销操作,命令模式可以存储历史命令,轻松实现撤销功能。
  • 队列请求: 命令模式可以将请求排队,形成一个命令队列,依次执行命令。
  • 可扩展性: 可以很容易地添加新的命令类和接收者类,而不影响现有的代码。新增命令不需要修改现有代码,符合开闭原则。

但是对于每个命令,都会有一个具体命令类,这可能导致类的数量急剧增加,增加了系统的复杂性。

命令模式同样有着很多现实场景的应用,比如Git中的很多操作,如提交(commit)、合并(merge)等,都可以看作是命令模式的应用,用户通过执行相应的命令来操作版本库。Java的GUI编程中,很多事件处理机制也都使用了命令模式。例如,每个按钮都有一个关联的 Action,它代表一个命令,按钮的点击触发 Action 的执行。