命令模式是什么
把请求封装成对象:请求的参数、接收者、执行方式都收进一个对象里,于是可以排队、记录、重放、撤销,也可以把多个命令组合成宏命令。命令对象只封装意图与数据,真正的业务仍在接收者身上。编辑器的撤销栈、任务队列、事务脚本是它最常见的三个落点。
基本信息
| 意图 | 把请求对象化:可参数化、排队、撤销 |
|---|---|
| 组成 | Command 接口 + Receiver + Invoker + Client |
| 应用 | 撤销重做、任务队列、事务脚本、宏命令 |
| 要点 | 命令只封装意图,业务留在接收者 |
典型使用场景
- 编辑器撤销重做:每个操作保存为命令对象,压入栈后按序回滚
- 任务队列:命令对象可序列化后投递,由工作进程取出执行
- 批量操作:一组命令组合成宏命令,整体执行或整体撤销
- 操作日志与审计:命令天然带有参数,便于记录与回放
- 延迟执行:把命令先收集起来,在事务提交或某个时机统一执行
Java 示例
Java 示例
interface Command { void execute(); void undo(); }
class BoldCommand implements Command {
private final Document doc;
BoldCommand(Document doc) { this.doc = doc; }
@Override public void execute() { doc.bold(); }
@Override public void undo() { doc.unbold(); } // 与 execute 对称
}
class MacroCommand implements Command {
private final List<Command> cmds;
MacroCommand(List<Command> cmds) { this.cmds = cmds; }
@Override public void execute() { cmds.forEach(Command::execute); }
@Override public void undo() { // 逆序回滚
for (int i = cmds.size() - 1; i >= 0; i--) cmds.get(i).undo();
}
}角色职责
| 角色 | 职责 | 常见错误 |
|---|---|---|
| Command | 封装请求参数与执行方式 | 把业务规则写进命令 |
| Receiver | 真正执行操作的对象 | 被命令绕过,逻辑散在两处 |
| Invoker | 触发命令、维护历史栈 | 直接调用接收者,绕过命令 |
| Client | 装配命令与接收者 | 让命令自己去找接收者 |
undo 与 execute 必须对称:只记录部分状态、或回滚时漏掉副作用,会让撤销后的状态与执行前不一致,这类问题往往在连续多次撤销后才暴露,测试要覆盖多次往返。
命令对象持有业务逻辑是本模式的常见误用:接收者已有的能力被复制一份进命令,之后修改规则要改两处。命令应当薄到可以只看参数就明白它会做什么。