命令模式(Command):把"请求"变成可存储的对象
更新时间:2026-08-25。本文按"先看不用模式的困境 → 再用模式重构 → 类图对比分析"的结构,论述命令模式。可运行代码:demos/design-patterns/command。
一、问题场景:请求要能被排队、撤销、记录、延迟执行
编辑器撤销/重做、任务队列调度、宏录制、遥控器按钮映射操作。"要做什么"(请求)与"谁来做、何时做"要解耦——请求要作为对象被存储和传递。
二、不使用模式:发起者直接调用执行者
cpp
class Editor {
public:
void copy() { /* ... */ } void paste() { /* ... */ }
};
// 快捷键直接调方法:无法撤销、无法宏录制、无法排队
Editor ed; ed.paste(); // 撤销?宏?队列?都没有| 缺陷 | 说明 |
|---|---|
| 无撤销能力 | 操作本身不可逆,没有"反向操作"记录 |
| 无法排队/延迟 | 请求与执行绑定,不能积压调度 |
| 无法宏录制 | 操作序列不能变成数据 |
| 发起者耦合执行者 | 按钮/菜单直接依赖具体对象方法 |
不使用模式的类图

三、使用模式:请求封装成命令对象
命令模式把"一次操作"封装为命令对象(execute() + 可选 undo());发起者(Invoker)只触发命令,接收者(Receiver)执行真正逻辑,命令在两者之间充当可存储的载体——队列、撤销栈、宏、日志都围绕命令对象展开。
cpp
// command.h
class Command { // 命令抽象
public:
virtual void execute() = 0;
virtual void undo() {}
virtual ~Command() = default;
};
class Editor { // 接收者:真正干活
public:
void doPaste() { /* 粘贴逻辑 */ }
void undoPaste() { /* 撤销粘贴 */ }
};
class PasteCommand : public Command { // 具体命令
public:
explicit PasteCommand(Editor& e) : editor_(e) {}
void execute() override { editor_.doPaste(); history_.push_back(shared_from_this()); }
void undo() override { editor_.undoPaste(); }
private:
Editor& editor_;
};
// 撤销栈 + 命令队列都是普通容器
std::vector<Command*> undoStack;
class Toolbar { // 发起者:只触发命令
public:
void setCmd(Command* c) { cmd_ = c; }
void click() { cmd_->execute(); }
private:
Command* cmd_;
};
// 使用
Editor ed; PasteCommand paste(ed);
Toolbar tb; tb.setCmd(&paste); tb.click(); // 触发
undoStack.push_back(&paste); // 可撤销使用模式的类图

四、类图对比与分析
| 维度 | 不使用(直接调用) | 使用命令 |
|---|---|---|
| 撤销/重做 | 无 | undo 栈天然支持 |
| 排队/延迟 | 无 | 命令入队即调度 |
| 宏录制 | 无 | 记录命令序列回放 |
| 发起者耦合 | 直接依赖具体类 | 只依赖 Command 抽象 |
| 结构成本 | 无 | 每个操作一个命令类(类爆炸风险) |
分析结论:命令把"请求"数据化,从而获得撤销、队列、日志、宏四项能力——这是直接调用做不到的。代价是每个操作一个命令类,类数量膨胀;可用 lambda + std::function 简化(C++ 中常见"函数对象式命令")。
五、适用边界
| 适用 | 不适用 |
|---|---|
| 需要撤销/重做、操作队列 | 操作无状态、不需回放(直接调用即可) |
| 需要宏录制、事务日志 | 命令类数量失控(评估 lambda 方案) |
| 发起者与执行者需彻底解耦 | 发起者即执行者(命令是绕圈) |
与策略的区别:策略封装"算法"(怎么算);命令封装"请求"(做什么 + 可撤销)。与观察者的区别:观察者"通知"(状态广播);命令"触发"(明确指定动作)。
一句话总结
命令模式把请求封装成可存储的命令对象,发起者只触发、接收者只管执行,撤销/排队/宏/日志围绕命令对象自然展开——请求数据化带来撤销、队列、宏等能力;代价是操作多时命令类爆炸,可结合函数对象精简。