命令模式(Command Pattern)详解
一、核心定义 & 本质
官方定义
将一个请求封装为一个对象,从而使你可以:
- 用不同请求对客户进行参数化
- 对请求进行排队或记录日志
- 支持可撤销/重做的操作
** 通俗大白话**
把 「做什么」 和 「谁去做」 彻底分离。
命令对象就像 遥控器按钮:既知道要执行什么动作,又自带“如何撤销”的说明书。
调用者只负责 按下按钮,不关心 灯泡怎么亮,实现极致解耦。
二、五大角色 & 结构示意图
1. 角色映射表
| 角色 | 英文 | 职责 | 生活类比 |
|---|---|---|---|
| 命令接口 | Command | 声明 execute() / undo() | 遥控器统一接口 |
| 具体命令 | ConcreteCommand | 绑定接收者,实现执行/撤销逻辑 | “开灯按钮”、“换台按钮” |
| 调用者 | Invoker | 持有命令对象,触发 execute() | 遥控器外壳/主板 |
| 接收者 | Receiver | 真正执行具体业务逻辑 | 灯泡、电视、数据库 |
| 客户端 | Client | 组装命令、绑定接收者、交给调用者 | 用户/配置脚本 |
2. 类结构图 (Mermaid)
三、工作原理 (执行时序)
四、优缺点 & 适用场景
1. 优缺点对照
| 维度 | 说明 |
|---|---|
| 优点 | ① 调用者与接收者彻底解耦 ② 新增命令只需实现接口,符合开闭原则 ③ 天然支持撤销/重做/排队/日志/事务 ④ 支持组合命令(宏命令)一键执行多步操作 |
| 缺点 | ① 每个命令一个类,可能导致 类爆炸 ② 引入额外抽象层,初期调试成本略高 ③ 过度使用会使简单逻辑变复杂 |
2. 适用场景
- GUI 按钮/菜单/快捷键与业务逻辑分离
- 需要撤销/重做:文本编辑器、绘图软件、IDE
- 任务队列/日志记录:消息队列、操作审计
- 事务处理:数据库事务、工作流引擎(全成功或全回滚)
- 宏命令/脚本化:一键执行“保存+编译+运行”
五、代码实现 (Java & C++)
Java 实现:智能家居遥控器
// 1. 命令接口
public interface Command {
void execute();
void undo();
}
// 2. 接收者:灯光
public class Light {
private final String location;
public Light(String location) { this.location = location; }
public void on() { System.out.println(location + " 灯光已打开"); }
public void off() { System.out.println(location + " 灯光已关闭"); }
}
// 3. 具体命令:开灯
public class LightOnCommand implements Command {
private final Light light;
public LightOnCommand(Light light) { this.light = light; }
@Override public void execute() { light.on(); }
@Override public void undo() { light.off(); }
}
// 4. 调用者:遥控器
public class RemoteControl {
private Command command;
public void setCommand(Command command) { this.command = command; }
public void pressButton() { if(command != null) command.execute(); }
public void pressUndo() { if(command != null) command.undo(); }
}
// 5. 客户端测试
public class Client {
public static void main(String[] args) {
Light livingRoom = new Light("客厅");
Command on = new LightOnCommand(livingRoom);
Command off = new LightOffCommand(livingRoom); // 假设已定义
RemoteControl remote = new RemoteControl();
remote.setCommand(on); remote.pressButton(); // 开
remote.pressUndo(); // 关(撤销)
}
}
C++ 现代实现(智能指针+多态)
#include <iostream>
#include <memory>
using namespace std;
class Command {
public:
virtual void execute() = 0;
virtual void undo() = 0;
virtual ~Command() = default;
};
class TV {
public:
void on() { cout << "电视已打开\n"; }
void off() { cout << "电视已关闭\n"; }
void setChannel(int ch) { channel = ch; cout << "切换至频道 " << ch << "\n"; }
int getChannel() const { return channel; }
private:
int channel = 0;
};
class TVOnCommand : public Command {
public:
explicit TVOnCommand(TV* tv) : tv(tv) {}
void execute() override { tv->on(); }
void undo() override { tv->off(); }
private:
TV* tv;
};
class TVChangeChannelCommand : public Command {
public:
TVChangeChannelCommand(TV* tv, int newCh) : tv(tv), newCh(newCh), oldCh(0) {}
void execute() override { oldCh = tv->getChannel(); tv->setChannel(newCh); }
void undo() override { tv->setChannel(oldCh); }
private:
TV* tv;
int newCh, oldCh;
};
class RemoteControl {
public:
void setCommand(unique_ptr<Command> cmd) { this->cmd = move(cmd); }
void press() { if(cmd) cmd->execute(); }
void undo() { if(cmd) cmd->undo(); }
private:
unique_ptr<Command> cmd;
};
int main() {
TV tv;
RemoteControl remote;
remote.setCommand(make_unique<TVOnCommand>(&tv));
remote.press(); remote.undo();
remote.setCommand(make_unique<TVChangeChannelCommand>(&tv, 5));
remote.press(); remote.undo();
return 0;
}
六、真实场景深度拆解(面试高频)
场景1:文本编辑器(撤销/重做)
核心逻辑:每一步操作 = 独立命令对象。编辑器维护两个栈。
** 不用命令模式的后果**:编辑器源码写满 if(type==DELETE) / if(type==INPUT),新增操作必改核心类,严重违反开闭原则。
** 一句话背诵**
编辑器把每一步操作打包成命令,用栈存起来。Ctrl+Z 调 undo,Ctrl+Y 重调 execute。
场景2:数据库事务(原子性保证)
核心逻辑:每条 SQL = 命令对象。事务管理器维护命令队列,支持 commit / rollback。
** 为什么是命令模式?**
- 上层业务只关心
commit/rollback,不写死每条 SQL 怎么回滚 - 事务管理器 =
Invoker,SQL 命令 =ConcreteCommand - 批量执行 + 批量撤销 = 命令队列天然能力
** 一句话背诵**
事务把SQL打包成命令队列。commit 顺序执行,rollback 倒序撤销,保证原子性。
七、与其他模式对比 & 面试避坑
1. 易混淆模式对比
| 模式 | 核心目的 | 关键区别 |
|---|---|---|
| 命令模式 | 解耦请求发起者与执行者,支持撤销/排队 | 请求是对象,可存储、传递、回滚 |
| 策略模式 | 封装算法族,运行时切换执行逻辑 | 侧重算法替换,无撤销/排队概念 |
| 备忘录模式 | 保存对象内部状态,用于恢复 | 侧重状态快照,不封装行为逻辑 |
2. 面试高频陷阱
- “命令模式就是写个接口让调用者调用”
→ 必须包含execute与undo/rollback,且能脱离接收者独立存在/序列化。 - “撤销就是备忘录模式”
→ 命令的undo是反向执行逻辑(如insert对应delete),备忘录是直接恢复旧状态。 - 宏命令/组合命令:通过
Composite模式将多个命令组合成一个大命令,一键执行(如“保存全部+关闭文档”)。
转载自CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/2503_90237760/article/details/161185318



