命令模式是一种行为型设计模式,其核心思想是将 “请求” 封装成一个独立的 “命令对象”,该对象包含执行请求所需的所有信息(如请求的接收者、执行参数等),从而实现 “请求的发送者” 与 “请求的接收者” 解耦 —— 发送者无需知道接收者的具体实现,只需调用命令对象的执行方法即可。

该模式的本质是 “封装请求为对象” ,通过命令对象作为中间载体,支持请求的存储、排队、撤销(Undo)、重做(Redo)等高级功能,同时让发送者和接收者完全独立。

UML结构如下

image.png

命令模式的典型应用场景如下:

  1. 需要解耦请求发送者与接收者

    • GUI 界面操作:如按钮(调用者)、菜单(调用者)触发 “打开窗口”“保存文件”(命令),窗口 / 文件处理器(接收者)执行实际操作,按钮无需知道窗口如何打开。
    • 遥控器控制:如电视遥控器(调用者)的 “开机”“换台” 按钮(命令),电视(接收者)执行对应操作,遥控器无需知道电视的内部电路逻辑。
  2. 需要支持命令的撤销 / 重做

    • 文本编辑器:如 “输入文字”“删除文字” 命令,可通过 undo() 撤销上一步操作,通过 redo() 重做已撤销的操作。
    • 绘图软件:如 “画直线”“画圆形” 命令,支持撤销最近绘制的图形。
  3. 需要批量处理或排队执行命令

    • 任务调度系统:如定时任务(命令)被加入任务队列(调用者维护的命令列表),到时间后批量执行所有任务。
    • 数据库事务:如一组 SQL 操作(命令)被封装成事务,要么全部执行(execute()),要么全部回滚(undo())。

下面我将用 Java 代码实现命令模式,以文件操作(打开和关闭文件)为例,展示命令模式的具体应用。

  1. Command(抽象命令接口)

    • 定义了命令的统一接口,包含执行命令的execute()方法和撤销命令的undo()方法
    • 是所有具体命令的抽象父类
1
2
3
4
5
6
7
8
9
// 抽象命令接口
public interface Command {
// 执行命令
void execute();

// 撤销命令
void undo();
}

  1. 具体命令类(OpenFileCommand 和 CloseFileCommand)

    • 实现了 Command 接口,持有对命令接收者(FileReceiver)的引用
    • execute()方法中调用接收者的相应方法来完成实际操作
    • undo()方法中调用接收者的撤销方法来恢复到操作前的状态
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 具体命令:打开文件
public class OpenFileCommand implements Command {
// 持有命令接收者引用
private FileReceiver fileReceiver;

// 构造器注入接收者
public OpenFileCommand(FileReceiver fileReceiver) {
this.fileReceiver = fileReceiver;
}

// 执行命令:调用接收者的打开文件方法
@Override
public void execute() {
fileReceiver.openFile();
}

// 撤销命令:调用接收者的撤销打开方法
@Override
public void undo() {
fileReceiver.undoOpenFile();
}
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 具体命令:关闭文件
public class CloseFileCommand implements Command {
// 持有命令接收者引用
private FileReceiver fileReceiver;

// 构造器注入接收者
public CloseFileCommand(FileReceiver fileReceiver) {
this.fileReceiver = fileReceiver;
}

// 执行命令:调用接收者的关闭文件方法
@Override
public void execute() {
fileReceiver.closeFile();
}

// 撤销命令:调用接收者的撤销关闭方法
@Override
public void undo() {
fileReceiver.undoCloseFile();
}
}

  1. FileReceiver(命令接收者)

    • 是实际执行命令的对象,包含打开文件、关闭文件及对应的撤销方法
    • 维护文件的状态(是否打开),用于支持撤销操作
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
// 命令接收者:文件处理器(实际执行文件操作)
public class FileReceiver {
private String fileName;
// 记录文件当前状态,用于撤销操作
private boolean isOpen;

public FileReceiver(String fileName) {
this.fileName = fileName;
this.isOpen = false;
}

// 实际业务方法:打开文件
public void openFile() {
if (!isOpen) {
System.out.println("打开文件:" + fileName);
isOpen = true;
} else {
System.out.println("文件 " + fileName + " 已经处于打开状态");
}
}

// 实际业务方法:关闭文件
public void closeFile() {
if (isOpen) {
System.out.println("关闭文件:" + fileName);
isOpen = false;
} else {
System.out.println("文件 " + fileName + " 已经处于关闭状态");
}
}

// 撤销打开:对应关闭文件
public void undoOpenFile() {
if (isOpen) {
System.out.println("撤销打开文件:" + fileName);
closeFile();
}
}

// 撤销关闭:对应打开文件
public void undoCloseFile() {
if (!isOpen) {
System.out.println("撤销关闭文件:" + fileName);
openFile();
}
}
}

  1. ButtonInvoker(命令调用者)

    • 持有命令对象的引用,提供setCommand()方法设置要执行的命令
    • 通过click()方法触发命令的执行,通过clickUndo()方法触发命令的撤销
    • 不直接与接收者交互,完全依赖命令对象完成操作
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
// 命令调用者:操作按钮(触发命令执行)
public class ButtonInvoker {
// 持有当前命令引用
private Command command;

// 设置要执行的命令
public void setCommand(Command command) {
this.command = command;
}

// 触发命令:点击按钮时执行命令
public void click() {
if (command != null) {
command.execute();
}
}

// 触发撤销:点击撤销按钮时执行undo
public void clickUndo() {
if (command != null) {
command.undo();
}
}
}

  1. Client(客户端)

    • 负责创建接收者、具体命令和调用者对象
    • 将接收者注入具体命令,将具体命令注入调用者,完成命令模式的组装
    • 触发命令的执行和撤销,测试整个模式的工作流程
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
// 客户端:测试命令模式
public class Client {
public static void main(String[] args) {
// 1. 创建命令接收者:文件处理器
FileReceiver fileReceiver = new FileReceiver("example.txt");

// 2. 创建具体命令,并绑定接收者
Command openCommand = new OpenFileCommand(fileReceiver);
Command closeCommand = new CloseFileCommand(fileReceiver);

// 3. 创建命令调用者:按钮
ButtonInvoker button = new ButtonInvoker();

// 4. 测试打开文件命令
System.out.println("=== 测试打开文件 ===");
button.setCommand(openCommand);
button.click(); // 执行打开命令
button.clickUndo(); // 撤销打开命令

// 5. 测试关闭文件命令
System.out.println("\n=== 测试关闭文件 ===");
button.setCommand(closeCommand);
button.click(); // 先打开文件(通过撤销关闭实现)
button.click(); // 执行关闭命令
button.clickUndo(); // 撤销关闭命令
}
}

上述代码执行流程如下:

  1. 客户端创建接收者对象,它知道如何执行具体的业务操作
  2. 客户端创建具体命令对象,并将接收者对象注入到命令中
  3. 客户端将具体命令对象设置到调用者中
  4. 当调用者触发命令(如按钮被点击)时,调用者调用命令的execute()方法
  5. 命令的execute()方法调用接收者的相应方法,完成实际操作
  6. 若需要撤销操作,调用者调用命令的undo()方法,命令会调用接收者的撤销方法

这种设计模式的优势在于:

  • 实现了请求发送者(调用者)和接收者之间的解耦
  • 支持命令的撤销和重做操作
  • 可以很容易地添加新的命令,符合开闭原则
  • 便于实现命令的排队执行、日志记录等高级功能