什么是命令模式?它解决什么问题?
简化版
命令模式是把一次请求封装成一个命令对象,让请求发起者不直接调用真正的执行者。它主要解决调用者和执行者耦合过紧的问题,并支持排队、日志、撤销、重做、异步执行等扩展能力。
详细版
命令模式的核心思想是:把“要做什么”封装成对象。
普通调用里,调用者通常直接写:
receiver.action();
用了命令模式后,调用者只需要执行命令:
command.execute();
真正由谁执行、怎么执行、能不能撤销,都被封装在命令对象内部。
命令模式常用于这些场景:
- 菜单按钮、快捷键、遥控器等 UI 操作;
- 任务队列和异步任务;
- 撤销、重做;
- 宏命令;
- 操作日志和审计;
- 事务型操作和补偿动作。
它的价值不是让简单调用变复杂,而是在“请求需要被存储、传递、排队、撤销或统一调度”时,把请求变成可管理的对象。
完整版教学
一、为什么要把请求封装成对象
在普通代码里,一次调用通常由调用方直接发起:
class Button {
private Light light;
public void click() {
light.on();
}
}
这段代码的问题是 Button 直接依赖 Light,按钮只能控制灯。如果明天按钮要控制风扇、空调、音响,就要修改按钮代码。
命令模式把“打开灯”这件事封装成对象:
interface Command {
void execute();
}
class LightOnCommand implements Command {
private final Light light;
LightOnCommand(Light light) {
this.light = light;
}
public void execute() {
light.on();
}
}
按钮只依赖 Command:
class Button {
private Command command;
public void click() {
command.execute();
}
}
这样按钮不关心命令背后控制的是灯、风扇还是别的设备。
二、命令模式解决的不是“调用”本身
如果只是把一行方法调用换成一个类,命令模式看起来会显得啰嗦。
它真正解决的是:请求需要被当成对象来管理。
对象可以被:
- 保存到列表里;
- 放进队列里;
- 延迟执行;
- 记录日志;
- 传递给其他模块;
- 组合成批量操作;
- 执行失败后补偿;
- 反向执行用于撤销。
这就是“请求对象化”的价值。
三、命令模式的关键解耦点
命令模式把系统拆成三层:
Invoker 调用命令
Command 封装请求
Receiver 真正执行业务
调用者只知道 Command 接口,不知道具体执行者。
例如遥控器只知道按钮绑定了一个命令,不知道命令内部是开电视、开灯还是调空调温度。
四、命令模式为什么适合撤销重做
撤销重做要求系统不仅知道“执行了什么”,还要知道“怎么反向操作”。
命令对象可以同时提供:
void execute();
void undo();
执行后把命令放入历史栈:
执行命令 -> 入撤销栈
撤销命令 -> 从撤销栈弹出并调用 undo
重做命令 -> 从重做栈弹出并调用 execute
如果没有命令对象,系统就很难统一管理各种不同操作的撤销逻辑。
五、命令模式不是每个业务都要用
简单 CRUD 直接调用 service 方法通常更清晰。
只有当请求具有“可管理性需求”时,命令模式才值得引入,例如:
- 请求要排队;
- 请求要异步执行;
- 请求要撤销;
- 请求要记录日志;
- 请求要统一调度;
- 请求要组合成批处理。
如果没有这些需求,硬套命令模式会增加类数量和理解成本。
六、常见误区与追问
是否需要命令模式,可以用时间和空间解耦来判断。调用者在 T0 创建“发送报表”命令,调度器在 T1 执行,重试器在 T2 再次处理;三者不必了解报表服务的具体接口。请求变成对象后,才能被保存、传输、组合和记录,但也必须承担参数冻结与失败语义。
| 检查维度 | 判定依据 |
|---|---|
| 直接调用 | 请求随方法返回而结束 |
| 命令对象 | 请求可跨时间保存和调度 |
记忆钩子:把动词连同参数装进对象,请求才有了可管理的生命周期。
- 误区:命令模式只是给方法调用包一层。 只有封装后获得解耦、排队、撤销等能力时,这层抽象才有意义。
- 追问:为什么它属于行为型模式? 它关注对象之间如何传递和执行请求,而不是创建对象或组织静态结构。
- 误区:命令对象必须自己完成所有业务。 它通常委托 Receiver,避免命令类演化成另一个业务服务。
- 追问:命令参数何时确定? 通常在创建命令时冻结,以保证延迟执行和重放时语义稳定。
- 追问:它解决调用者和谁的耦合? 主要隔离调用者与具体执行者、方法和参数组织方式。
七、加强记忆
命令模式的核心是把请求封装成命令对象,让调用者只负责触发命令,真正的业务执行交给接收者。它的价值在于让请求可以被保存、排队、组合、撤销、重做和统一调度,而不是单纯把方法调用包一层。