命令模式(Command Pattern)
命令模式是一种行为型设计模式,它将请求封装为对象,使你可以用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。这种模式的核心是 “将行为请求者与行为实现者解耦”,通过命令对象作为中间桥梁,实现二者的间接交互。
一、定义
命令模式(Command Pattern)的官方定义为: Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations. (将一个请求封装成一个对象,从而允许你用不同的请求对客户端进行参数化,对请求进行排队或记录日志,以及支持可撤销的操作。)
简单来说,命令模式把 “做什么”(请求)和 “谁去做”(实现)分离开,通过一个 “命令对象” 承载请求的细节,再由 “调用者” 触发命令,“接收者” 执行命令对应的操作。
二、意图
命令模式的核心意图可归纳为以下 3 点:
- 解耦请求者与接收者:请求者(如按钮、菜单)无需知道接收者(如具体业务逻辑类)的存在,也无需知道请求如何被执行,只需触发命令即可。
- 支持请求的灵活管理:可对命令进行排队(如任务队列)、记录日志(如操作审计)、缓存(如批量执行)。
- 支持可撤销 / 重做操作:通过保存命令的历史记录或反向操作,实现请求的撤销(Undo)和重做(Redo)。
三、结构
命令模式包含 5 个核心角色,各角色职责明确,协同完成 “请求封装与执行” 的流程:
| 角色名称 | 核心职责 |
|---|---|
| 命令接口(Command) | 定义命令的统一接口,通常包含一个执行方法(如 execute())和可选的撤销方法(如 undo())。 |
| 具体命令(ConcreteCommand) | 实现命令接口,绑定 “接收者” 和 “具体操作”,在 execute() 中调用接收者的对应方法。 |
| 接收者(Receiver) | 负责执行命令对应的具体业务逻辑(如 “打开文件”“关闭窗口”),是命令的实际执行者。 |
| 调用者(Invoker) | 持有命令对象,负责触发命令的执行(如 “点击按钮” 触发命令),无需关心命令的具体实现。 |
| 客户端(Client) | 负责创建 “具体命令” 和 “接收者”,并将接收者注入命令,最终将命令交给调用者执行。 |
四、优点
- 解耦请求者与接收者:请求者只需触发命令,无需知道接收者的存在或操作的具体实现,降低模块间耦合。
- 易扩展新命令:新增命令只需实现
Command接口,无需修改现有代码,符合开闭原则。 - 支持请求的灵活管理:可轻松实现命令排队(如线程池任务队列)、日志记录(如操作审计日志)、事务管理(如批量命令提交 / 回滚)。
- 支持撤销 / 重做:通过保存命令历史记录或在命令中定义反向操作(
undo()),可实现操作的撤销与重做。 - 命令可复用:同一个具体命令对象可被多个调用者重复使用(如 “复制” 命令可被菜单、快捷键等多个调用者触发)。
五、缺点
- 类数量膨胀:每一个具体操作都需要对应一个具体命令类,若系统中存在大量命令,会导致类数量急剧增加,增加维护成本。
- 复杂度提升:相比直接调用方法,命令模式引入了多个角色(命令、调用者、接收者),增加了系统的抽象度和理解复杂度。
- 简单场景下冗余:对于无需撤销、排队、日志的简单请求,使用命令模式会显得过度设计,不如直接调用方法简洁。
六、类图(Mermaid)
以下类图展示命令模式的核心角色关系(以 “遥控器控制电视” 为例:遥控器是调用者,电视是接收者,“打开电视”“关闭电视” 是具体命令):

classDiagram
direction TB
class Command {
<<interface>>
+execute() : void
+undo() : void
}
class ConcreteCommandOpen {
-receiver: TVReceiver
+ConcreteCommandOpen(receiver: TVReceiver)
+execute() : void
+undo() : void
}
class ConcreteCommandClose {
-receiver: TVReceiver
+ConcreteCommandClose(receiver: TVReceiver)
+execute() : void
+undo() : void
}
class TVReceiver {
+turnOn() : void
+turnOff() : void
}
class RemoteControlInvoker {
-command: Command
+setCommand(command: Command) : void
+pressButton() : void
+pressUndoButton() : void
}
class Client {
+main() : void
}
Command <|.. ConcreteCommandOpen
Command <|.. ConcreteCommandClose
ConcreteCommandOpen o-- TVReceiver : "持有"
ConcreteCommandClose o-- TVReceiver : "持有"
RemoteControlInvoker o-- Command : "持有"
Client ..> RemoteControlInvoker : "创建并设置命令"
Client ..> TVReceiver : "创建接收者"
Client ..> ConcreteCommandOpen : "创建具体命令"
Client ..> ConcreteCommandClose : "创建具体命令"
七、时序图(Mermaid)
以下时序图展示 “客户端通过遥控器打开电视” 的完整流程(包含命令执行和撤销操作):
sequenceDiagram
participant Client
participant RemoteControlInvoker as 遥控器(调用者)
participant ConcreteCommandOpen as 打开命令
participant TVReceiver as 电视(接收者)
%% 1. 客户端初始化:创建接收者、具体命令、调用者,并绑定关系
Client->>TVReceiver: 1. new TVReceiver() (创建接收者)
Client->>ConcreteCommandOpen: 2. new ConcreteCommandOpen(TVReceiver) (绑定接收者到命令)
Client->>RemoteControlInvoker: 3. new RemoteControlInvoker() (创建调用者)
Client->>RemoteControlInvoker: 4. setCommand(ConcreteCommandOpen) (调用者绑定命令)
%% 2. 触发命令执行(按下“打开”按钮)
Client->>RemoteControlInvoker: 5. 按下“打开”按钮 → pressButton()
RemoteControlInvoker->>ConcreteCommandOpen: 6. 调用 command.execute()
ConcreteCommandOpen->>TVReceiver: 7. 调用 receiver.turnOn()
TVReceiver-->>ConcreteCommandOpen: 8. 返回:电视已打开
ConcreteCommandOpen-->>RemoteControlInvoker: 9. 返回:命令执行完成
RemoteControlInvoker-->>Client: 10. 返回:操作成功
%% 3. 触发命令撤销(按下“撤销”按钮)
Client->>RemoteControlInvoker: 11. 按下“撤销”按钮 → pressUndoButton()
RemoteControlInvoker->>ConcreteCommandOpen: 12. 调用 command.undo()
ConcreteCommandOpen->>TVReceiver: 13. 调用 receiver.turnOff() (反向操作)
TVReceiver-->>ConcreteCommandOpen: 14. 返回:电视已关闭
ConcreteCommandOpen-->>RemoteControlInvoker: 15. 返回:撤销完成
RemoteControlInvoker-->>Client: 16. 返回:撤销成功
八、适用环境
当系统满足以下任一场景时,适合使用命令模式:
- 需要解耦请求者与接收者:如 GUI 界面中,按钮(请求者)无需知道点击后具体执行什么业务逻辑(接收者)。
- 需要支持请求的排队 / 日志 / 事务:如订单系统的 “提交订单”“支付”“发货” 命令需排队执行;操作日志需记录每一步命令的执行情况;事务需保证多个命令要么全部执行,要么全部回滚。
- 需要支持撤销 / 重做操作:如文本编辑器的 “撤销输入”“重做输入”、绘图软件的 “撤销画笔”“重做画笔”。
- 需要动态切换命令:如遥控器可切换 “打开电视”“打开空调” 等不同命令,只需更换命令对象即可。
九、模式分析
- 核心逻辑:命令模式的本质是 “将请求对象化”—— 每个请求不再是直接的方法调用,而是一个包含请求信息的对象。这个对象可以被传递、存储、修改,从而实现灵活的请求管理。
- 依赖关系:调用者(Invoker)依赖命令接口(Command),具体命令(ConcreteCommand)依赖接收者(Receiver),客户端(Client)负责组装三者的关系,符合 “依赖倒置原则”(依赖抽象,不依赖具体)。
- 撤销机制的实现:
- 方式 1:在具体命令中保存执行前的状态,
undo()方法恢复该状态(如文本编辑器保存修改前的文本内容)。 - 方式 2:
undo()方法执行execute()的反向操作(如 “打开电视” 的反向是 “关闭电视”)。
- 方式 1:在具体命令中保存执行前的状态,
十、模式扩展
命令模式可结合其他设计模式进行扩展,满足更复杂的需求:
- 结合备忘录模式(Memento):实现更灵活的撤销 / 重做。备忘录模式保存命令执行前的状态,命令模式的
undo()方法通过备忘录恢复状态(适合状态复杂的场景,如文档编辑)。 - 结合组合模式(Composite):实现 “宏命令”(Macro Command)。将多个命令组合成一个复合命令,调用者触发一次复合命令,即可执行多个子命令(如 “关机” 命令包含 “保存文档”“关闭窗口”“切断电源” 三个子命令)。
- 结合原型模式(Prototype):快速复制命令对象。若需重复执行相同参数的命令,可通过原型模式复制命令对象,避免重复创建。
- 结合观察者模式(Observer):命令执行后通知相关对象。如命令执行成功后,通知日志系统记录日志、通知 UI 更新状态。
十一、模式应用
命令模式在各类系统中应用广泛,典型场景包括:
- GUI 框架:如 Swing/AWT 中的
Action接口(本质是命令模式),按钮、菜单绑定Action对象,点击时触发actionPerformed()(类似execute())。 - 文本编辑器:“复制”“粘贴”“撤销”“重做” 等操作均封装为命令,通过命令历史记录实现撤销 / 重做。
- 遥控器 / 智能设备控制:遥控器(调用者)通过不同命令(如 “打开”“关闭”“调节音量”)控制电视、空调(接收者)。
- 任务调度系统:如 Quartz 任务调度框架,将定时任务封装为命令,调度器(调用者)按时间触发命令执行。
- 事务管理:如数据库事务,“提交”“回滚” 操作封装为命令,事务管理器(调用者)根据执行结果触发 “提交” 或 “回滚” 命令。
十二、Android 中的应用
在 Android 开发中,命令模式的应用场景非常普遍,以下是典型案例:
Runnable与线程池:Runnable接口本质是命令模式的简化版(无undo()方法),代表一个 “可执行的命令”。- 线程池(
ThreadPoolExecutor)是调用者,负责触发Runnable.run()(类似execute());具体业务逻辑(如网络请求、数据处理)是接收者。
View.OnClickListener:- 虽然
OnClickListener是回调接口,但逻辑上符合命令模式:View(如 Button)是调用者,onClick(View v)是命令执行方法,Activity/Fragment 中的具体业务逻辑是接收者。 - 扩展:若需支持 “点击撤销”(如按钮点击后触发弹窗,撤销时关闭弹窗),可将
onClick逻辑封装为完整的命令类(含execute()和undo())。
- 虽然
WorkManager任务调度:WorkRequest(如OneTimeWorkRequest)是具体命令,封装了后台任务(如数据同步、文件清理);WorkManager是调用者,负责触发任务执行;后台任务的具体逻辑是接收者。
- 撤销 / 重做场景:
- 如 Android 绘图应用(如 SketchBook)、文本编辑应用(如 WPS),通过命令模式保存用户操作的历史记录,实现 “撤销画笔”“重做输入” 等功能。
十三、代码实现(Java 版)
以 “遥控器控制电视” 为例,实现命令模式的完整代码,包含 “打开电视”“关闭电视” 命令及撤销功能:
1. 命令接口(Command)
定义命令的统一方法(执行 + 撤销):
// 命令接口
public interface Command {
// 执行命令
void execute();
// 撤销命令
void undo();
}
2. 接收者(TVReceiver)
负责执行具体业务逻辑(打开 / 关闭电视):
// 接收者:电视
public class TVReceiver {
// 打开电视
public void turnOn() {
System.out.println("电视已打开,正在播放节目...");
}
// 关闭电视
public void turnOff() {
System.out.println("电视已关闭,进入待机模式...");
}
}
3. 具体命令(ConcreteCommand)
实现命令接口,绑定接收者与具体操作:
// 具体命令1:打开电视
public class ConcreteCommandOpen implements Command {
// 持有接收者引用
private TVReceiver tvReceiver;
// 构造器注入接收者
public ConcreteCommandOpen(TVReceiver tvReceiver) {
this.tvReceiver = tvReceiver;
}
@Override
public void execute() {
// 调用接收者的“打开”方法
tvReceiver.turnOn();
}
@Override
public void undo() {
// 撤销:调用接收者的“关闭”方法(反向操作)
tvReceiver.turnOff();
}
}
// 具体命令2:关闭电视
public class ConcreteCommandClose implements Command {
private TVReceiver tvReceiver;
public ConcreteCommandClose(TVReceiver tvReceiver) {
this.tvReceiver = tvReceiver;
}
@Override
public void execute() {
tvReceiver.turnOff();
}
@Override
public void undo() {
// 撤销:调用接收者的“打开”方法(反向操作)
tvReceiver.turnOn();
}
}
4. 调用者(RemoteControlInvoker)
持有命令对象,触发命令执行与撤销:
// 调用者:遥控器
public class RemoteControlInvoker {
// 当前命令(可扩展为命令列表,支持多命令)
private Command currentCommand;
// 命令历史记录(用于多步撤销/重做)
private Stack<Command> commandHistory = new Stack<>();
// 设置当前命令
public void setCommand(Command command) {
this.currentCommand = command;
}
// 按下“执行”按钮:触发命令执行,并记录历史
public void pressExecuteButton() {
if (currentCommand != null) {
currentCommand.execute();
commandHistory.push(currentCommand); // 记录已执行的命令
} else {
System.out.println("未设置命令,请先绑定命令!");
}
}
// 按下“撤销”按钮:触发上一次命令的撤销
public void pressUndoButton() {
if (!commandHistory.isEmpty()) {
Command lastCommand = commandHistory.pop();
lastCommand.undo();
} else {
System.out.println("无历史命令可撤销!");
}
}
}
5. 客户端(Client)
组装角色关系,测试命令模式:
// 客户端:测试命令模式
public class CommandPatternClient {
public static void main(String[] args) {
// 1. 创建接收者(电视)
TVReceiver tv = new TVReceiver();
// 2. 创建具体命令(打开、关闭),并绑定接收者
Command openCommand = new ConcreteCommandOpen(tv);
Command closeCommand = new ConcreteCommandClose(tv);
// 3. 创建调用者(遥控器)
RemoteControlInvoker remote = new RemoteControlInvoker();
// 4. 测试“打开电视”命令
System.out.println("=== 测试打开电视 ===");
remote.setCommand(openCommand);
remote.pressExecuteButton(); // 执行:打开电视
// 5. 测试“撤销打开”
System.out.println("\n=== 测试撤销打开 ===");
remote.pressUndoButton(); // 撤销:关闭电视
// 6. 测试“关闭电视”命令
System.out.println("\n=== 测试关闭电视 ===");
remote.setCommand(closeCommand);
remote.pressExecuteButton(); // 执行:关闭电视
// 7. 测试“撤销关闭”
System.out.println("\n=== 测试撤销关闭 ===");
remote.pressUndoButton(); // 撤销:打开电视
}
}
6. 运行结果
=== 测试打开电视 ===
电视已打开,正在播放节目...
=== 测试撤销打开 ===
电视已关闭,进入待机模式...
=== 测试关闭电视 ===
电视已关闭,进入待机模式...
=== 测试撤销关闭 ===
电视已打开,正在播放节目...
十四、总结
命令模式通过 “将请求封装为对象”,实现了请求者与接收者的解耦,核心价值在于灵活管理请求(排队、日志、撤销)和易扩展新请求(符合开闭原则)。
- 核心优势:解耦、可扩展、支持撤销 / 排队 / 日志,适用于复杂的请求管理场景。
- 主要局限:类数量膨胀、简单场景下过度设计,需根据实际需求权衡使用。
- Android 实践建议:在需支持撤销(如编辑功能)、任务调度(如后台任务)、解耦 UI 与业务逻辑(如按钮点击)的场景中,可优先考虑命令模式,或借鉴其 “请求对象化” 的思想(如用
Runnable封装后台任务)。
命令模式是设计模式中 “封装思想” 的典型体现,理解它能帮助开发者更好地设计低耦合、高灵活的系统架构。