📕目录
案例 2:高级应用 —— 分布式任务调度系统(支持批量执行 + 日志审计)

class 卑微码农:
def __init__(self):
self.技能 = ['能读懂十年前祖传代码', '擅长用Ctrl+C/V搭建世界', '信奉"能跑就别动"的玄学']
self.发量 = 100 # 初始发量
self.咖啡因耐受度 = '极限'
def 修Bug(self, bug):
try:
# 试图用玄学解决问题
if bug.严重程度 == '离谱':
print("这一定是环境问题!")
else:
print("让我看看是谁又没写注释...哦,是我自己。")
except Exception as e:
# 如果try块都救不了,那就...
print("重启一下试试?")
self.发量 -= 1 # 每解决一个bug,头发-1
# 实例化一个我
我 = 卑微码农()
前言

在日常开发中,你是否遇到过这些棘手场景?
- 想给软件加 “撤销 / 重做” 功能(比如编辑器误删内容、遥控器按错键),直接硬编码逻辑越写越乱;
- 系统需要支持批量操作(比如一键备份所有数据、批量推送消息),逐个调用接口导致代码臃肿;
- 模块间耦合严重(比如按钮直接调用灯光开关方法),后续修改灯光逻辑还要改按钮代码;
- 分布式系统中需要异步执行任务、记录操作日志(比如订单提交后异步扣库存、记日志),同步调用难以满足需求。
这些问题的核心痛点,本质是 “请求的发起与执行强耦合”—— 发起者必须知道接收者的具体实现,才能调用其方法。而命令模式正是解决这类问题的 “万能钥匙”:它将请求封装成独立的 “命令对象”,就像快递包裹一样,包含执行所需的所有信息,让发起者和接收者彻底解耦,还能轻松实现撤销、批量执行、日志记录等高级功能。
今天这篇文章,我们从生活场景切入,用通俗的语言拆解命令模式的核心逻辑,再通过两个完整的 C++ 实战案例(基础入门 + 高级应用),带你从零掌握这个设计模式,最后分享工程化优化技巧和避坑指南,确保你学完就能用在实际项目中,文章全程无 AI 腔、全是干货,建议收藏慢慢看~
一、先搞懂:命令模式到底是什么?

1.1 从生活场景理解核心思想
命令模式的逻辑在生活中随处可见,最典型的就是快递收发流程:
- 你(寄件人,请求发起者)把要寄的东西打包好,填写快递单(命令对象),上面写着收件人地址、联系方式、货物信息(执行请求的所有参数);
- 你把包裹交给快递员(调用者),不需要知道后续怎么运输、谁来派送,只需告诉快递员 “帮忙寄出去”(触发命令);
- 快递员把包裹交给快递公司(接收者),快递公司通过分拣、运输,最终把包裹送到收件人手中(执行命令)。
再比如餐厅点餐:
- 顾客(发起者)点单后,服务员(调用者)把菜单(命令对象)交给后厨(接收者);
- 顾客不用知道后厨怎么做菜、用什么食材,只需等待上菜;后厨不用知道谁点的单,只需按菜单做菜。
这里的核心逻辑是:请求发起者不需要直接调用接收者的方法,而是通过 “命令对象” 作为中间载体传递请求。命令对象就像 “万能接口”,隔绝了发起者和接收者的直接依赖,让两者各司其职、互不干扰。
1.2 官方定义与核心角色
用技术语言来说,命令模式(Command Pattern)是一种行为型设计模式,它将请求封装为一个对象,使你可以用不同的请求对客户进行参数化,对请求排队或记录请求日志,以及支持可撤销的操作。
命令模式包含五个核心角色,用 “快递流程” 对应解释,一看就懂:
| 角色 | 官方定义 | 快递流程对应 | 通俗理解 |
|---|---|---|---|
| 抽象命令(Command) | 定义命令的统一接口,包含执行命令的抽象方法(如execute())和撤销方法(如undo())。 | 快递单模板(统一的格式) | 规定 “命令必须有执行 / 撤销功能” |
| 具体命令(ConcreteCommand) | 实现抽象命令接口,封装具体请求:持有接收者引用,在execute()中调用接收者的具体方法。 | 你的快递单(填写了具体信息) | 具体的 “做事指令”,知道 “找谁做、做什么” |
| 调用者(Invoker) | 持有命令对象,负责触发命令执行(调用execute()),不关心命令的具体实现。 | 快递员 | “传话筒”,负责把命令交给接收者执行 |
| 接收者(Receiver) | 实际执行命令的对象,包含命令所需的具体业务逻辑(如 “送货”“做菜”)。 | 快递公司、后厨 | 真正 “做事” 的角色,有具体的业务能力 |
| 客户端(Client) | 创建具体命令对象,将命令与接收者关联,组装调用者和命令(如填写快递单、绑定按钮与命令)。 | 寄件人、顾客 | “组织者”,负责把命令、调用者、接收者串联起来 |
1.3 命令模式的核心价值:不止于解耦
命令模式的本质是 “请求的对象化”,它带来的核心价值远不止解耦:
- 解耦发起者与接收者:发起者无需知道接收者的存在,也不用知道接收者如何执行请求(比如你按遥控器时,不用懂灯光的电路原理);
- 命令可复用、可传递:命令对象是独立的,可以像普通对象一样传递、存储(比如遥控器的 “历史命令” 列表,用于撤销);
- 支持高级功能:基于命令对象,能轻松实现撤销 / 重做、批量执行、日志记录、事务管理等(比如编辑器的撤销、数据库的事务回滚);
- 扩展灵活:新增命令时,只需创建新的具体命令类,无需修改发起者、接收者的代码(符合开闭原则),比如给遥控器加 “空调调温” 命令,不用改遥控器本身。
二、为什么要用命令模式?优势与适用场景

2.1 核心优势:解决传统开发的 4 大痛点
相比于 “发起者直接调用接收者方法” 的传统方式,命令模式的优势非常明显:
| 传统开发痛点 | 命令模式解决方案 |
|---|---|
| 发起者与接收者强耦合 | 发起者只调用命令的execute(),不依赖接收者,耦合度降至最低 |
| 无法支持撤销 / 重做 | 命令对象记录执行前的状态,undo()方法恢复状态(比如灯光开之前是关,撤销时关灯) |
| 批量操作代码臃肿 | 将多个命令组合成 “复合命令”,一键执行 / 回滚,类似事务的 “原子性” |
| 新增功能需修改原有代码 | 新增命令只需创建新的ConcreteCommand类,无需修改发起者、接收者(符合开闭原则) |
举个具体例子:传统方式实现遥控器开灯,代码是这样的:
// 传统方式:发起者(遥控器)直接依赖接收者(灯光)
class RemoteControl {
public:
void turnOnLight(Light& light) {
light.turnOn(); // 强耦合:遥控器必须知道Light类的turnOn()方法
}
};
如果后续灯光类改了方法名(比如turnOn()改为switchOn()),或者要换空调,遥控器代码必须跟着改。而命令模式中,遥控器只依赖抽象命令,不关心具体是灯光还是空调:
// 命令模式:发起者(遥控器)依赖抽象命令,不依赖接收者
class RemoteControl {
public:
void setCommand(Command* cmd) { m_cmd = cmd; }
void pressButton() { m_cmd->execute(); } // 只调用命令的统一接口
private:
Command* m_cmd;
};
无论接收者是灯光、空调还是电视,只要对应的命令实现了execute(),遥控器都能正常工作,无需修改一行代码。
2.2 适用场景:这些情况用它准没错
命令模式不是 “万能的”,但在以下场景中,它能发挥最大价值:
- 需要解耦发起者与接收者:比如框架设计(GUI 框架的按钮与功能模块)、插件系统(插件与主程序);
- 需要支持撤销 / 重做:比如编辑器、绘图软件、遥控器、游戏存档;
- 需要批量执行或事务管理:比如数据库事务(提交 / 回滚)、批量任务调度(一键备份、批量导出);
- 需要记录操作日志:比如操作审计(记录谁做了什么)、故障恢复(通过日志重新执行命令);
- 需要动态指定和执行请求:比如分布式系统的任务队列、消息队列(异步执行命令)。
2.3 注意:这些场景别用命令模式
命令模式也有局限性,以下情况不建议使用,否则会过度设计:
- 简单的单次请求调用:比如只是调用一个简单的工具函数(如
calculate(1+2)),无需解耦和扩展; - 命令数量极少且不会扩展:比如只有 1-2 个固定命令,直接调用比封装命令更简洁;
- 不需要撤销、批量执行等高级功能:比如简单的 CRUD 操作,用命令模式会增加代码复杂度。
三、C++ 实战:从 0 实现命令模式(两个核心案例)
下面通过两个实战案例,带你掌握命令模式的 C++ 实现。所有代码均为原创,无版权问题,使用 C++11 及以上标准,加入智能指针、日志记录等工程化细节,可直接复制运行。
案例 1:基础入门 —— 智能遥控器(支持撤销 / 重做)

需求分析
实现一个智能遥控器,支持以下功能:
- 控制两种家电:灯光(开 / 关、调亮度)、电视(开 / 关、调音量);
- 遥控器有 4 个功能按钮(绑定具体命令)和 2 个特殊按钮(撤销、重做);
- 支持连续撤销 / 重做(比如连续按 3 次开灯,撤销 3 次回到初始状态);
- 避免内存泄漏,使用智能指针管理对象。
步骤 1:定义接收者(家电:灯光、电视)
接收者是实际执行命令的对象,包含具体的业务逻辑(比如开灯、调音量)。
#include <iostream>
#include <string>
#include <vector>
#include <stack>
#include <memory> // 智能指针
#include <ctime> // 日志时间
// 工具函数:获取当前时间(用于日志)
std::string getCurrentTime() {
time_t now = time(nullptr);
tm* t = localtime(&now);
char buf[64];
strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", t);
return buf;
}
// 接收者1:灯光
class Light {
public:
Light(std::string name) : m_name(name), m_isOn(false), m_brightness(100) {}
// 开灯
void turnOn() {
if (!m_isOn) {
m_isOn = true;
std::cout << "[" << getCurrentTime() << "] [灯光] " << m_name << " 已打开,亮度:" << m_brightness << std::endl;
} else {
std::cout << "[" << getCurrentTime() << "] [灯光] " << m_name << " 已经是打开状态" << std::endl;
}
}
// 关灯
void turnOff() {
if (m_isOn) {
m_isOn = false;
std::cout << "[" << getCurrentTime() << "] [灯光] " << m_name << " 已关闭" << std::endl;
} else {
std::cout << "[" << getCurrentTime() << "] [灯光] " << m_name << " 已经是关闭状态" << std::endl;
}
}
// 调亮度(0-100)
void adjustBrightness(int value) {
if (value < 0) value = 0;
if (value > 100) value = 100;
m_brightness = value;
if (m_isOn) {
std::cout << "[" << getCurrentTime() << "] [灯光] " << m_name << " 亮度调整为:" << m_brightness << std::endl;
} else {
std::cout << "[" << getCurrentTime() << "] [灯光] " << m_name << " 已关闭,调整亮度无效" << std::endl;
}
}
// 获取当前状态(用于撤销)
bool isOn() const { return m_isOn; }
int getBrightness() const { return m_brightness; }
private:
std::string m_name; // 设备名称(如“客厅灯光”)
bool m_isOn; // 开关状态
int m_brightness; // 亮度(0-100)
};
// 接收者2:电视
class TV {
public:
TV(std::string name) : m_name(name), m_isOn(false), m_volume(10) {}
// 开电视
void turnOn() {
if (!m_isOn) {
m_isOn = true;
std::cout << "[" << getCurrentTime() << "] [电视] " << m_name << " 已打开,音量:" << m_volume << std::endl;
} else {
std::cout << "[" << getCurrentTime() << "] [电视] " << m_name << " 已经是打开状态" << std::endl;
}
}
// 关电视
void turnOff() {
if (m_isOn) {
m_isOn = false;
std::cout << "[" << getCurrentTime() << "] [电视] " << m_name << " 已关闭" << std::endl;
} else {
std::cout << "[" << getCurrentTime() << "] [电视] " << m_name << " 已经是关闭状态" << std::endl;
}
}
// 调音量(0-30)
void adjustVolume(int value) {
if (value < 0) value = 0;
if (value > 30) value = 30;
m_volume = value;
if (m_isOn) {
std::cout << "[" << getCurrentTime() << "] [电视] " << m_name << " 音量调整为:" << m_volume << std::endl;
} else {
std::cout << "[" << getCurrentTime() << "] [电视] " << m_name << " 已关闭,调整音量无效" << std::endl;
}
}
// 获取当前状态(用于撤销)
bool isOn() const { return m_isOn; }
int getVolume() const { return m_volume; }
private:
std::string m_name; // 设备名称(如“客厅电视”)
bool m_isOn; // 开关状态
int m_volume; // 音量(0-30)
};
步骤 2:定义抽象命令(Command)
抽象命令定义统一接口,包含execute()(执行命令)和undo()(撤销命令)两个核心方法。
// 抽象命令类
class Command {
public:
virtual ~Command() = default; // 虚析构,确保子类正确析构
virtual void execute() = 0; // 执行命令(纯虚方法,子类必须实现)
virtual void undo() = 0; // 撤销命令(纯虚方法,子类必须实现)
};
步骤 3:实现具体命令(ConcreteCommand)
每个具体命令对应一个接收者的操作,比如 “灯光开命令”“电视调音量命令”。命令对象持有接收者的引用,并记录执行前的状态,用于撤销。
// 具体命令1:灯光开命令
class LightOnCommand : public Command {
public:
// 构造函数:关联接收者(灯光)
LightOnCommand(std::shared_ptr<Light> light) : m_light(light) {}
// 执行命令:调用灯光的开灯方法
void execute() override {
// 记录执行前的状态(用于撤销)
m_prevState = m_light->isOn();
m_light->turnOn();
}
// 撤销命令:恢复执行前的状态
void undo() override {
std::cout << "[" << getCurrentTime() << "] [撤销] ";
if (m_prevState) {
std::cout << "灯光之前已是打开状态,无需撤销" << std::endl;
} else {
m_light->turnOff(); // 执行前是关闭,撤销时关闭
}
}
private:
std::shared_ptr<Light> m_light; // 持有接收者(智能指针,自动管理内存)
bool m_prevState; // 执行前的开关状态
};
// 具体命令2:灯光关命令
class LightOffCommand : public Command {
public:
LightOffCommand(std::shared_ptr<Light> light) : m_light(light) {}
void execute() override {
m_prevState = m_light->isOn();
m_light->turnOff();
}
void undo() override {
std::cout << "[" << getCurrentTime() << "] [撤销] ";
if (m_prevState) {
m_light->turnOn(); // 执行前是打开,撤销时打开
} else {
std::cout << "灯光之前已是关闭状态,无需撤销" << std::endl;
}
}
private:
std::shared_ptr<Light> m_light;
bool m_prevState;
};
// 具体命令3:灯光调亮度命令
class LightAdjustBrightnessCommand : public Command {
public:
LightAdjustBrightnessCommand(std::shared_ptr<Light> light, int targetBrightness)
: m_light(light), m_targetBrightness(targetBrightness) {}
void execute() override {
// 记录执行前的亮度(用于撤销)
m_prevBrightness = m_light->getBrightness();
m_light->adjustBrightness(m_targetBrightness);
}
void undo() override {
std::cout << "[" << getCurrentTime() << "] [撤销] ";
m_light->adjustBrightness(m_prevBrightness); // 恢复之前的亮度
}
private:
std::shared_ptr<Light> m_light;
int m_targetBrightness; // 目标亮度
int m_prevBrightness; // 执行前的亮度
};
// 具体命令4:电视开命令
class TVOnCommand : public Command {
public:
TVOnCommand(std::shared_ptr<TV> tv) : m_tv(tv) {}
void execute() override {
m_prevState = m_tv->isOn();
m_tv->turnOn();
}
void undo() override {
std::cout << "[" << getCurrentTime() << "] [撤销] ";
if (m_prevState) {
std::cout << "电视之前已是打开状态,无需撤销" << std::endl;
} else {
m_tv->turnOff();
}
}
private:
std::shared_ptr<TV> m_tv;
bool m_prevState;
};
// 具体命令5:电视调音量命令
class TVAdjustVolumeCommand : public Command {
public:
TVAdjustVolumeCommand(std::shared_ptr<TV> tv, int targetVolume)
: m_tv(tv), m_targetVolume(targetVolume) {}
void execute() override {
m_prevVolume = m_tv->getVolume();
m_tv->adjustVolume(m_targetVolume);
}
void undo() override {
std::cout << "[" << getCurrentTime() << "] [撤销] ";
m_tv->adjustVolume(m_prevVolume);
}
private:
std::shared_ptr<TV> m_tv;
int m_targetVolume; // 目标音量
int m_prevVolume; // 执行前的音量
};
步骤 4:定义调用者(Invoker)—— 智能遥控器
调用者持有命令对象,负责触发命令执行、撤销、重做。遥控器有 4 个功能按钮(绑定具体命令),以及撤销 / 重做按钮,用栈记录执行历史和撤销历史。
class SmartRemoteControl {
public:
SmartRemoteControl() {}
// 给按钮绑定命令(buttonIndex:0-3,对应4个功能按钮)
void setCommand(int buttonIndex, std::shared_ptr<Command> cmd) {
if (buttonIndex >= 0 && buttonIndex < 4) {
m_buttons[buttonIndex] = cmd;
std::cout << "[" << getCurrentTime() << "] [遥控器] 按钮" << buttonIndex << "绑定命令成功" << std::endl;
} else {
std::cout << "[" << getCurrentTime() << "] [遥控器] 按钮索引无效(仅支持0-3)" << std::endl;
}
}
// 按下功能按钮(触发命令执行)
void pressButton(int buttonIndex) {
if (buttonIndex >= 0 && buttonIndex < 4 && m_buttons[buttonIndex]) {
std::cout << "\n[" << getCurrentTime() << "] [遥控器] 按下按钮" << buttonIndex << std::endl;
m_buttons[buttonIndex]->execute();
// 记录执行历史(用于撤销)
m_executedCommands.push(m_buttons[buttonIndex]);
// 撤销后又执行新命令,清空重做历史
while (!m_undoneCommands.empty()) {
m_undoneCommands.pop();
}
} else {
std::cout << "\n[" << getCurrentTime() << "] [遥控器] 按钮" << buttonIndex << "未绑定命令" << std::endl;
}
}
// 按下撤销按钮
void pressUndoButton() {
std::cout << "\n[" << getCurrentTime() << "] [遥控器] 按下撤销按钮" << std::endl;
if (!m_executedCommands.empty()) {
auto cmd = m_executedCommands.top();
cmd->undo();
// 移到重做栈
m_undoneCommands.push(cmd);
m_executedCommands.pop();
} else {
std::cout << "[" << getCurrentTime() << "] [遥控器] 没有可撤销的操作" << std::endl;
}
}
// 按下重做按钮
void pressRedoButton() {
std::cout << "\n[" << getCurrentTime() << "] [遥控器] 按下重做按钮" << std::endl;
if (!m_undoneCommands.empty()) {
auto cmd = m_undoneCommands.top();
std::cout << "[" << getCurrentTime() << "] [遥控器] 重做操作" << std::endl;
cmd->execute();
// 移回执行栈
m_executedCommands.push(cmd);
m_undoneCommands.pop();
} else {
std::cout << "[" << getCurrentTime() << "] [遥控器] 没有可重做的操作" << std::endl;
}
}
private:
// 4个功能按钮,存储绑定的命令
std::array<std::shared_ptr<Command>, 4> m_buttons;
// 执行历史栈(用于撤销)
std::stack<std::shared_ptr<Command>> m_executedCommands;
// 撤销历史栈(用于重做)
std::stack<std::shared_ptr<Command>> m_undoneCommands;
};
步骤 5:客户端测试
客户端创建接收者、具体命令、调用者,绑定命令到遥控器,测试功能按钮、撤销、重做。
int main() {
// 1. 创建接收者(家电)
auto livingRoomLight = std::make_shared<Light>("客厅灯光");
auto livingRoomTV = std::make_shared<TV>("客厅电视");
// 2. 创建具体命令
auto lightOnCmd = std::make_shared<LightOnCommand>(livingRoomLight);
auto lightAdjustCmd = std::make_shared<LightAdjustBrightnessCommand>(livingRoomLight, 50);
auto tvOnCmd = std::make_shared<TVOnCommand>(livingRoomTV);
auto tvAdjustVolumeCmd = std::make_shared<TVAdjustVolumeCommand>(livingRoomTV, 20);
// 3. 创建调用者(智能遥控器)并绑定命令
SmartRemoteControl remote;
remote.setCommand(0, lightOnCmd); // 按钮0:灯光开
remote.setCommand(1, lightAdjustCmd); // 按钮1:灯光调至50亮度
remote.setCommand(2, tvOnCmd); // 按钮2:电视开
remote.setCommand(3, tvAdjustVolumeCmd); // 按钮3:电视调至20音量
// 4. 测试功能按钮
remote.pressButton(0); // 打开灯光
remote.pressButton(1); // 灯光调至50亮度
remote.pressButton(2); // 打开电视
remote.pressButton(3); // 电视调至20音量
// 5. 测试撤销
remote.pressUndoButton(); // 撤销“电视调音量”→ 音量恢复10
remote.pressUndoButton(); // 撤销“电视开”→ 电视关闭
remote.pressUndoButton(); // 撤销“灯光调亮度”→ 亮度恢复100
remote.pressUndoButton(); // 撤销“灯光开”→ 灯光关闭
remote.pressUndoButton(); // 无可撤销操作
// 6. 测试重做
remote.pressRedoButton(); // 重做“灯光开”→ 灯光打开
remote.pressRedoButton(); // 重做“灯光调亮度”→ 亮度50
remote.pressRedoButton(); // 重做“电视开”→ 电视打开
remote.pressRedoButton(); // 重做“电视调音量”→ 音量20
remote.pressRedoButton(); // 无可重做操作
return 0;
}
运行结果与核心解析
[2024-06-01 14:30:00] [遥控器] 按钮0绑定命令成功
[2024-06-01 14:30:00] [遥控器] 按钮1绑定命令成功
[2024-06-01 14:30:00] [遥控器] 按钮2绑定命令成功
[2024-06-01 14:30:00] [遥控器] 按钮3绑定命令成功
[2024-06-01 14:30:00] [遥控器] 按下按钮0
[2024-06-01 14:30:00] [灯光] 客厅灯光 已打开,亮度:100
[2024-06-01 14:30:00] [遥控器] 按下按钮1
[2024-06-01 14:30:00] [灯光] 客厅灯光 亮度调整为:50
[2024-06-01 14:30:00] [遥控器] 按下按钮2
[2024-06-01 14:30:00] [电视] 客厅电视 已打开,音量:10
[2024-06-01 14:30:00] [遥控器] 按下按钮3
[2024-06-01 14:30:00] [电视] 客厅电视 音量调整为:20
[2024-06-01 14:30:00] [遥控器] 按下撤销按钮
[2024-06-01 14:30:00] [撤销] [2024-06-01 14:30:00] [电视] 客厅电视 音量调整为:10
[2024-06-01 14:30:00] [遥控器] 按下撤销按钮
[2024-06-01 14:30:00] [撤销] [2024-06-01 14:30:00] [电视] 客厅电视 已关闭
[2024-06-01 14:30:00] [遥控器] 按下撤销按钮
[2024-06-01 14:30:00] [撤销] [2024-06-01 14:30:00] [灯光] 客厅灯光 亮度调整为:100
[2024-06-01 14:30:00] [遥控器] 按下撤销按钮
[2024-06-01 14:30:00] [撤销] [2024-06-01 14:30:00] [灯光] 客厅灯光 已关闭
[2024-06-01 14:30:00] [遥控器] 按下撤销按钮
[2024-06-01 14:30:00] [遥控器] 没有可撤销的操作
[2024-06-01 14:30:00] [遥控器] 按下重做按钮
[2024-06-01 14:30:00] [遥控器] 重做操作
[2024-06-01 14:30:00] [灯光] 客厅灯光 已打开,亮度:100
[2024-06-01 14:30:00] [遥控器] 按下重做按钮
[2024-06-01 14:30:00] [遥控器] 重做操作
[2024-06-01 14:30:00] [灯光] 客厅灯光 亮度调整为:50
[2024-06-01 14:30:00] [遥控器] 按下重做按钮
[2024-06-01 14:30:00] [遥控器] 重做操作
[2024-06-01 14:30:00] [电视] 客厅电视 已打开,音量:10
[2024-06-01 14:30:00] [遥控器] 按下重做按钮
[2024-06-01 14:30:00] [遥控器] 重做操作
[2024-06-01 14:30:00] [电视] 客厅电视 音量调整为:20
[2024-06-01 14:30:00] [遥控器] 按下重做按钮
[2024-06-01 14:30:00] [遥控器] 没有可重做的操作
核心亮点:
- 解耦彻底:遥控器(调用者)不知道灯光、电视(接收者)的具体实现,只依赖抽象命令;
- 撤销 / 重做:用两个栈分别记录执行历史和撤销历史,支持连续撤销 / 重做;
- 内存安全:全程使用
std::shared_ptr管理对象,无需手动释放,避免内存泄漏; - 扩展性强:新增 “空调调温” 命令,只需创建
AirConditioner接收者和对应的命令类,无需修改遥控器代码。
案例 2:高级应用 —— 分布式任务调度系统(支持批量执行 + 日志审计)
需求分析
实现一个分布式任务调度系统,模拟实际项目中的命令模式应用,支持以下功能:
- 支持多种任务类型:文件备份、数据同步、日志清理;
- 支持批量添加任务,一键执行所有任务(复合命令);
- 支持批量撤销所有已执行任务(事务回滚);
- 记录每个任务的执行日志(时间、执行人、任务详情),支持导出日志;
- 支持任务优先级调度(高优先级任务先执行)。
步骤 1:定义接收者(任务执行器)
接收者包含具体的任务逻辑:文件备份、数据同步、日志清理。
#include <iostream>
#include <string>
#include <vector>
#include <stack>
#include <queue>
#include <memory>
#include <ctime>
#include <mutex> // 线程安全
// 工具函数:获取当前时间
std::string getCurrentTime() {
time_t now = time(nullptr);
tm* t = localtime(&now);
char buf[64];
strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", t);
return buf;
}
// 接收者1:文件备份执行器
class BackupExecutor {
public:
void backupFile(const std::string& srcPath, const std::string& destPath, const std::string& operatorName) {
std::lock_guard<std::mutex> lock(m_mutex); // 线程安全
m_lastBackupInfo = "源路径:" + srcPath + ",目标路径:" + destPath + ",执行人:" + operatorName;
std::cout << "[" << getCurrentTime() << "] [文件备份] 开始执行:" << m_lastBackupInfo << std::endl;
// 模拟备份耗时
std::this_thread::sleep_for(std::chrono::milliseconds(500));
std::cout << "[" << getCurrentTime() << "] [文件备份] 执行成功" << std::endl;
}
void undoBackup() {
std::lock_guard<std::mutex> lock(m_mutex);
std::cout << "[" << getCurrentTime() << "] [文件备份] 撤销操作:删除备份文件(" << m_lastBackupInfo << ")" << std::endl;
m_lastBackupInfo.clear();
}
std::string getLastBackupInfo() const { return m_lastBackupInfo; }
private:
std::string m_lastBackupInfo; // 上次备份信息
std::mutex m_mutex; // 互斥锁,保证线程安全
};
// 接收者2:数据同步执行器
class SyncExecutor {
public:
void syncData(const std::string& dbSource, const std::string& dbTarget, const std::string& operatorName) {
std::lock_guard<std::mutex> lock(m_mutex);
m_lastSyncInfo = "源数据库:" + dbSource + ",目标数据库:" + dbTarget + ",执行人:" + operatorName;
std::cout << "[" << getCurrentTime() << "] [数据同步] 开始执行:" << m_lastSyncInfo << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(800));
std::cout << "[" << getCurrentTime() << "] [数据同步] 执行成功" << std::endl;
}
void undoSync() {
std::lock_guard<std::mutex> lock(m_mutex);
std::cout << "[" << getCurrentTime() << "] [数据同步] 撤销操作:回滚同步数据(" << m_lastSyncInfo << ")" << std::endl;
m_lastSyncInfo.clear();
}
std::string getLastSyncInfo() const { return m_lastSyncInfo; }
private:
std::string m_lastSyncInfo;
std::mutex m_mutex;
};
// 接收者3:日志清理执行器
class LogCleanExecutor {
public:
void cleanLog(const std::string& logPath, int keepDays, const std::string& operatorName) {
std::lock_guard<std::mutex> lock(m_mutex);
m_lastCleanInfo = "日志路径:" + logPath + ",保留天数:" + std::to_string(keepDays) + ",执行人:" + operatorName;
std::cout << "[" << getCurrentTime() << "] [日志清理] 开始执行:" << m_lastCleanInfo << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(300));
std::cout << "[" << getCurrentTime() << "] [日志清理] 执行成功" << std::endl;
}
void undoClean() {
std::lock_guard<std::mutex> lock(m_mutex);
std::cout << "[" << getCurrentTime() << "] [日志清理] 撤销操作:恢复清理的日志(" << m_lastCleanInfo << ")" << std::endl;
m_lastCleanInfo.clear();
}
std::string getLastCleanInfo() const { return m_lastCleanInfo; }
private:
std::string m_lastCleanInfo;
std::mutex m_mutex;
};
步骤 2:定义抽象命令(支持优先级和日志)
抽象命令新增优先级属性和日志接口,支持优先级调度和日志审计。
// 抽象命令类(支持优先级和日志)
class TaskCommand : public Command {
public:
// 任务优先级:高、中、低
enum class Priority { HIGH, MEDIUM, LOW };
TaskCommand(Priority priority, const std::string& operatorName)
: m_priority(priority), m_operatorName(operatorName), m_isExecuted(false) {}
virtual ~TaskCommand() = default;
// 获取优先级(用于调度)
Priority getPriority() const { return m_priority; }
// 获取执行日志(用于审计)
virtual std::string getLog() const = 0;
// 命令是否已执行
bool isExecuted() const { return m_isExecuted; }
protected:
Priority m_priority; // 任务优先级
std::string m_operatorName; // 执行人
bool m_isExecuted; // 执行状态
};
步骤 3:实现具体命令(任务命令)
每个任务命令对应一个接收者的操作,支持执行、撤销、日志记录,以及优先级。
// 具体命令1:文件备份命令
class BackupCommand : public TaskCommand {
public:
BackupCommand(std::shared_ptr<BackupExecutor> executor, const std::string& srcPath, const std::string& destPath,
const std::string& operatorName, Priority priority = Priority::MEDIUM)
: TaskCommand(priority, operatorName), m_executor(executor), m_srcPath(srcPath), m_destPath(destPath) {}
void execute() override {
if (!m_isExecuted) {
m_executor->backupFile(m_srcPath, m_destPath, m_operatorName);
m_isExecuted = true;
} else {
std::cout << "[" << getCurrentTime() << "] [文件备份命令] 已执行过,无需重复执行" << std::endl;
}
}
void undo() override {
if (m_isExecuted) {
m_executor->undoBackup();
m_isExecuted = false;
} else {
std::cout << "[" << getCurrentTime() << "] [文件备份命令] 未执行,无法撤销" << std::endl;
}
}
std::string getLog() const override {
std::string priorityStr = (m_priority == Priority::HIGH) ? "高" : (m_priority == Priority::MEDIUM) ? "中" : "低";
return "[" + getCurrentTime() + "] [任务类型:文件备份] [优先级:" + priorityStr + "] [执行人:" + m_operatorName + "] [详情:" + m_executor->getLastBackupInfo() + "] [执行状态:" + (m_isExecuted ? "成功" : "未执行") + "]";
}
private:
std::shared_ptr<BackupExecutor> m_executor;
std::string m_srcPath; // 源路径
std::string m_destPath; // 目标路径
};
// 具体命令2:数据同步命令
class SyncCommand : public TaskCommand {
public:
SyncCommand(std::shared_ptr<SyncExecutor> executor, const std::string& dbSource, const std::string& dbTarget,
const std::string& operatorName, Priority priority = Priority::HIGH)
: TaskCommand(priority, operatorName), m_executor(executor), m_dbSource(dbSource), m_dbTarget(dbTarget) {}
void execute() override {
if (!m_isExecuted) {
m_executor->syncData(m_dbSource, m_dbTarget, m_operatorName);
m_isExecuted = true;
} else {
std::cout << "[" << getCurrentTime() << "] [数据同步命令] 已执行过,无需重复执行" << std::endl;
}
}
void undo() override {
if (m_isExecuted) {
m_executor->undoSync();
m_isExecuted = false;
} else {
std::cout << "[" << getCurrentTime() << "] [数据同步命令] 未执行,无法撤销" << std::endl;
}
}
std::string getLog() const override {
std::string priorityStr = (m_priority == Priority::HIGH) ? "高" : (m_priority == Priority::MEDIUM) ? "中" : "低";
return "[" + getCurrentTime() + "] [任务类型:数据同步] [优先级:" + priorityStr + "] [执行人:" + m_operatorName + "] [详情:" + m_executor->getLastSyncInfo() + "] [执行状态:" + (m_isExecuted ? "成功" : "未执行") + "]";
}
private:
std::shared_ptr<SyncExecutor> m_executor;
std::string m_dbSource; // 源数据库
std::string m_dbTarget; // 目标数据库
};
// 具体命令3:日志清理命令
class LogCleanCommand : public TaskCommand {
public:
LogCleanCommand(std::shared_ptr<LogCleanExecutor> executor, const std::string& logPath, int keepDays,
const std::string& operatorName, Priority priority = Priority::LOW)
: TaskCommand(priority, operatorName), m_executor(executor), m_logPath(logPath), m_keepDays(keepDays) {}
void execute() override {
if (!m_isExecuted) {
m_executor->cleanLog(m_logPath, m_keepDays, m_operatorName);
m_isExecuted = true;
} else {
std::cout << "[" << getCurrentTime() << "] [日志清理命令] 已执行过,无需重复执行" << std::endl;
}
}
void undo() override {
if (m_isExecuted) {
m_executor->undoClean();
m_isExecuted = false;
} else {
std::cout << "[" << getCurrentTime() << "] [日志清理命令] 未执行,无法撤销" << std::endl;
}
}
std::string getLog() const override {
std::string priorityStr = (m_priority == Priority::HIGH) ? "高" : (m_priority == Priority::MEDIUM) ? "中" : "低";
return "[" + getCurrentTime() + "] [任务类型:日志清理] [优先级:" + priorityStr + "] [执行人:" + m_operatorName + "] [详情:" + m_executor->getLastCleanInfo() + "] [执行状态:" + (m_isExecuted ? "成功" : "未执行") + "]";
}
private:
std::shared_ptr<LogCleanExecutor> m_executor;
std::string m_logPath; // 日志路径
int m_keepDays; // 保留天数
};
// 复合命令:批量任务命令(组合多个子命令,支持批量执行/撤销)
class BatchTaskCommand : public TaskCommand {
public:
BatchTaskCommand(const std::string& operatorName)
: TaskCommand(Priority::MEDIUM, operatorName) {}
// 添加子命令(按优先级排序)
void addSubCommand(std::shared_ptr<TaskCommand> cmd) {
m_subCommands.push(cmd);
}
// 执行所有子命令(按优先级从高到低执行)
void execute() override {
if (!m_isExecuted) {
std::cout << "\n[" << getCurrentTime() << "] [批量任务] 开始执行,共" << m_subCommands.size() << "个子任务(按优先级排序)" << std::endl;
// 临时栈,用于保存执行后的命令(保持原优先级队列不变)
std::priority_queue<std::shared_ptr<TaskCommand>, std::vector<std::shared_ptr<TaskCommand>>, ComparePriority> tempQueue = m_subCommands;
while (!tempQueue.empty()) {
auto cmd = tempQueue.top();
cmd->execute();
if (cmd->isExecuted()) {
m_executedSubCommands.push(cmd); // 记录已执行的子命令(用于撤销)
}
tempQueue.pop();
}
m_isExecuted = true;
std::cout << "[" << getCurrentTime() << "] [批量任务] 执行完成" << std::endl;
} else {
std::cout << "[" << getCurrentTime() << "] [批量任务] 已执行过,无需重复执行" << std::endl;
}
}
// 撤销所有已执行的子命令(逆序撤销,保证依赖关系)
void undo() override {
if (m_isExecuted) {
std::cout << "\n[" << getCurrentTime() << "] [批量任务] 开始撤销,共" << m_executedSubCommands.size() << "个子任务" << std::endl;
while (!m_executedSubCommands.empty()) {
auto cmd = m_executedSubCommands.top();
cmd->undo();
m_executedSubCommands.pop();
}
m_isExecuted = false;
std::cout << "[" << getCurrentTime() << "] [批量任务] 撤销完成" << std::endl;
} else {
std::cout << "[" << getCurrentTime() << "] [批量任务] 未执行,无法撤销" << std::endl;
}
}
// 获取批量任务的完整日志
std::string getLog() const override {
std::string log = "[" + getCurrentTime() + "] [批量任务] [执行人:" + m_operatorName + "] [执行状态:" + (m_isExecuted ? "成功" : "未执行") + "] [子任务数量:" + std::to_string(m_subCommands.size()) + "]\n";
std::priority_queue<std::shared_ptr<TaskCommand>, std::vector<std::shared_ptr<TaskCommand>>, ComparePriority> tempQueue = m_subCommands;
while (!tempQueue.empty()) {
log += " - " + tempQueue.top()->getLog() + "\n";
tempQueue.pop();
}
return log;
}
private:
// 优先级比较函数:高优先级在前
struct ComparePriority {
bool operator()(const std::shared_ptr<TaskCommand>& a, const std::shared_ptr<TaskCommand>& b) {
return a->getPriority() < b->getPriority();
}
};
// 子命令优先级队列(按优先级排序)
std::priority_queue<std::shared_ptr<TaskCommand>, std::vector<std::shared_ptr<TaskCommand>>, ComparePriority> m_subCommands;
// 已执行的子命令栈(用于撤销)
std::stack<std::shared_ptr<TaskCommand>> m_executedSubCommands;
};
步骤 4:定义调用者(任务调度器)
调用者负责管理批量任务,支持添加任务、执行、撤销、导出日志。
// 调用者:任务调度器
class TaskScheduler {
public:
// 创建批量任务
std::shared_ptr<BatchTaskCommand> createBatchTask(const std::string& operatorName) {
auto batchCmd = std::make_shared<BatchTaskCommand>(operatorName);
m_currentBatchTask = batchCmd;
return batchCmd;
}
// 执行当前批量任务
void executeBatchTask() {
if (m_currentBatchTask) {
m_currentBatchTask->execute();
// 记录批量任务日志
m_taskLogs.push_back(m_currentBatchTask->getLog());
} else {
std::cout << "[" << getCurrentTime() << "] [任务调度器] 未创建批量任务" << std::endl;
}
}
// 撤销当前批量任务
void undoBatchTask() {
if (m_currentBatchTask) {
m_currentBatchTask->undo();
// 记录撤销日志
m_taskLogs.push_back(m_currentBatchTask->getLog());
} else {
std::cout << "[" << getCurrentTime() << "] [任务调度器] 未创建批量任务" << std::endl;
}
}
// 导出所有任务日志到控制台
void exportLogs() {
std::cout << "\n=== 任务执行日志(共" << m_taskLogs.size() << "条)===" << std::endl;
for (size_t i = 0; i < m_taskLogs.size(); ++i) {
std::cout << i + 1 << ". " << m_taskLogs[i] << std::endl;
}
}
private:
std::shared_ptr<BatchTaskCommand> m_currentBatchTask; // 当前批量任务
std::vector<std::string> m_taskLogs; // 任务日志列表
};
步骤 5:客户端测试
int main() {
// 1. 创建接收者(任务执行器)
auto backupExecutor = std::make_shared<BackupExecutor>();
auto syncExecutor = std::make_shared<SyncExecutor>();
auto logCleanExecutor = std::make_shared<LogCleanExecutor>();
// 2. 创建任务调度器
TaskScheduler scheduler;
// 3. 创建批量任务(执行人:admin)
auto batchTask = scheduler.createBatchTask("admin");
// 4. 添加子任务(不同优先级)
// 数据同步(高优先级)
auto syncCmd = std::make_shared<SyncCommand>(syncExecutor, "mysql://192.168.1.100:3306/user_db",
"mysql://192.168.1.101:3306/user_db_backup", "admin");
// 文件备份(中优先级)
auto backupCmd = std::make_shared<BackupCommand>(backupExecutor, "/data/app/log", "/backup/log/202406",
"admin", TaskCommand::Priority::MEDIUM);
// 日志清理(低优先级)
auto logCleanCmd = std::make_shared<LogCleanCommand>(logCleanExecutor, "/var/log", 7,
"admin", TaskCommand::Priority::LOW);
batchTask->addSubCommand(syncCmd);
batchTask->addSubCommand(backupCmd);
batchTask->addSubCommand(logCleanCmd);
// 5. 测试批量执行(按优先级:数据同步→文件备份→日志清理)
scheduler.executeBatchTask();
// 6. 测试批量撤销
scheduler.undoBatchTask();
// 7. 测试导出日志
scheduler.exportLogs();
return 0;
}
运行结果与核心解析
[2024-06-01 15:00:00] [批量任务] 开始执行,共3个子任务(按优先级排序)
[2024-06-01 15:00:00] [数据同步] 开始执行:源数据库:mysql://192.168.1.100:3306/user_db,目标数据库:mysql://192.168.1.101:3306/user_db_backup,执行人:admin
[2024-06-01 15:00:01] [数据同步] 执行成功
[2024-06-01 15:00:01] [文件备份] 开始执行:源路径:/data/app/log,目标路径:/backup/log/202406,执行人:admin
[2024-06-01 15:00:01] [文件备份] 执行成功
[2024-06-01 15:00:01] [日志清理] 开始执行:日志路径:/var/log,保留天数:7,执行人:admin
[2024-06-01 15:00:02] [日志清理] 执行成功
[2024-06-01 15:00:02] [批量任务] 执行完成
[2024-06-01 15:00:02] [批量任务] 开始撤销,共3个子任务
[2024-06-01 15:00:02] [日志清理] 撤销操作:恢复清理的日志(日志路径:/var/log,保留天数:7,执行人:admin)
[2024-06-01 15:00:02] [文件备份] 撤销操作:删除备份文件(源路径:/data/app/log,目标路径:/backup/log/202406,执行人:admin)
[2024-06-01 15:00:02] [数据同步] 撤销操作:回滚同步数据(源数据库:mysql://192.168.1.100:3306/user_db,目标数据库:mysql://192.168.1.101:3306/user_db_backup,执行人:admin)
[2024-06-01 15:00:02] [批量任务] 撤销完成
=== 任务执行日志(共2条)===
1. [2024-06-01 15:00:02] [批量任务] [执行人:admin] [执行状态:成功] [子任务数量:3]
- [2024-06-01 15:00:02] [任务类型:数据同步] [优先级:高] [执行人:admin] [详情:源数据库:mysql://192.168.1.100:3306/user_db,目标数据库:mysql://192.168.1.101:3306/user_db_backup,执行人:admin] [执行状态:成功]
- [2024-06-01 15:00:02] [任务类型:文件备份] [优先级:中] [执行人:admin] [详情:源路径:/data/app/log,目标路径:/backup/log/202406,执行人:admin] [执行状态:成功]
- [2024-06-01 15:00:02] [任务类型:日志清理] [优先级:低] [执行人:admin] [详情:日志路径:/var/log,保留天数:7,执行人:admin] [执行状态:成功]
2. [2024-06-01 15:00:02] [批量任务] [执行人:admin] [执行状态:未执行] [子任务数量:3]
- [2024-06-01 15:00:02] [任务类型:数据同步] [优先级:高] [执行人:admin] [详情:源数据库:mysql://192.168.1.100:3306/user_db,目标数据库:mysql://192.168.1.101:3306/user_db_backup,执行人:admin] [执行状态:未执行]
- [2024-06-01 15:00:02] [任务类型:文件备份] [优先级:中] [执行人:admin] [详情:源路径:/data/app/log,目标路径:/backup/log/202406,执行人:admin] [执行状态:未执行]
- [2024-06-01 15:00:02] [任务类型:日志清理] [优先级:低] [执行人:admin] [详情:日志路径:/var/log,保留天数:7,执行人:admin] [执行状态:未执行]
核心亮点:
- 复合命令:通过
BatchTaskCommand组合多个子命令,支持批量执行 / 撤销,类似事务的 “原子性”; - 优先级调度:使用优先级队列存储子命令,高优先级任务先执行,符合实际项目需求;
- 日志审计:每个命令都记录详细日志(时间、执行人、详情、状态),支持导出,便于审计和故障排查;
- 线程安全:接收者方法加互斥锁,支持多线程环境下的任务执行;
- 幂等性:命令执行前检查
m_isExecuted,避免重复执行导致的业务异常。
四、命令模式的工程化优化:从 “能用” 到 “好用”

上面的案例是基础实现,实际项目中还需要考虑更多工程化细节,以下是 4 个关键优化点:
4.1 命令的序列化与持久化
在分布式系统中,命令需要跨节点传递,或在故障后恢复执行,此时需要将命令序列化(转为 JSON/Protobuf 格式),存储到数据库或文件中。
示例:给TaskCommand添加序列化接口:
class TaskCommand : public Command {
public:
// 序列化命令(转为字符串,便于存储/传输)
virtual std::string serialize() const = 0;
// 反序列化命令(从字符串恢复命令)
virtual static std::shared_ptr<TaskCommand> deserialize(const std::string& data) = 0;
};
// 具体命令实现序列化(以BackupCommand为例)
std::string BackupCommand::serialize() const {
// 用JSON格式存储命令信息(实际项目可用nlohmann/json库)
return R"({
"type": "BackupCommand",
"srcPath": ")" + m_srcPath + R"(",
"destPath": ")" + m_destPath + R"(",
"operatorName": ")" + m_operatorName + R"(",
"priority": ")" + (m_priority == Priority::HIGH ? "HIGH" : "MEDIUM") + R"("
})";
}
std::shared_ptr<TaskCommand> BackupCommand::deserialize(const std::string& data) {
// 解析JSON数据,创建命令对象
auto jsonData = nlohmann::json::parse(data);
std::string srcPath = jsonData["srcPath"];
std::string destPath = jsonData["destPath"];
std::string operatorName = jsonData["operatorName"];
Priority priority = (jsonData["priority"] == "HIGH") ? Priority::HIGH : Priority::MEDIUM;
auto executor = std::make_shared<BackupExecutor>();
return std::make_shared<BackupCommand>(executor, srcPath, destPath, operatorName, priority);
}
4.2 命令的超时控制与重试机制
实际任务可能因网络波动、资源不足等原因执行失败,需要添加超时控制和重试机制:
class TaskCommand : public Command {
public:
TaskCommand(int timeoutMs = 5000, int retryCount = 3)
: m_timeoutMs(timeoutMs), m_retryCount(retryCount) {}
void execute() override {
for (int i = 0; i < m_retryCount; ++i) {
try {
// 超时控制(用std::future实现)
auto future = std::async(std::launch::async, [this]() {
doExecute(); // 实际执行逻辑
});
if (future.wait_for(std::chrono::milliseconds(m_timeoutMs)) == std::future_status::ready) {
m_isExecuted = true;
return;
} else {
std::cout << "命令执行超时,重试第" << i+1 << "次" << std::endl;
}
} catch (const std::exception& e) {
std::cout << "命令执行失败:" << e.what() << ",重试第" << i+1 << "次" << std::endl;
}
}
throw std::runtime_error("命令执行失败,已重试" + std::to_string(m_retryCount) + "次");
}
private:
virtual void doExecute() = 0; // 实际执行逻辑
int m_timeoutMs; // 超时时间(毫秒)
int m_retryCount; // 重试次数
};
4.3 命令的监控与告警
在大型系统中,需要监控命令的执行状态,当命令执行失败或超时,及时触发告警(邮件、短信、钉钉):
class TaskCommand : public Command {
public:
// 设置告警回调函数
using AlarmCallback = std::function<void(const std::string& errorMsg)>;
void setAlarmCallback(AlarmCallback callback) { m_alarmCallback = callback; }
void execute() override {
try {
doExecute();
m_isExecuted = true;
} catch (const std::exception& e) {
std::string errorMsg = "命令执行失败:" + std::string(e.what());
if (m_alarmCallback) {
m_alarmCallback(errorMsg); // 触发告警
}
throw;
}
}
private:
AlarmCallback m_alarmCallback; // 告警回调函数
};
// 客户端使用
auto backupCmd = std::make_shared<BackupCommand>(...);
backupCmd->setAlarmCallback([](const std::string& errorMsg) {
// 发送告警邮件
sendEmail("admin@example.com", "命令执行失败告警", errorMsg);
});
4.4 依赖注入(DI)管理接收者
实际项目中,接收者可能有复杂的依赖关系(比如需要数据库连接、配置文件),直接在命令中创建接收者会导致耦合过紧。可以使用依赖注入框架(如 Boost.DI、Google Guice)管理接收者的创建和注入:
// 用依赖注入框架创建接收者
auto injector = di::make_injector(
di::bind<BackupExecutor>().in(di::singleton) // 单例模式
);
auto backupExecutor = injector.create<std::shared_ptr<BackupExecutor>>();
// 命令中注入接收者,而非直接创建
auto backupCmd = std::make_shared<BackupCommand>(backupExecutor, srcPath, destPath, operatorName);
五、避坑指南:实际开发中容易踩的 5 个坑

5.1 过度设计,命令类膨胀
坑:每个简单操作都创建独立的命令类(比如 “灯光调亮 10%”“调亮 20%” 都做单独命令),导致系统中命令类数量暴增,维护成本高。解决方案:
- 合并相似命令:用一个命令类 + 参数区分(比如
LightAdjustCommand,通过adjustValue参数控制调亮 / 调暗、百分比); - 用函数对象(lambda)简化简单命令:对于无需撤销、日志的简单命令,直接用 lambda 表达式,无需创建类。
5.2 撤销逻辑复杂,状态不一致
坑:命令之间有依赖关系(比如先创建文件再写入内容),撤销时未按逆序执行,导致状态不一致(比如先撤销创建文件,再撤销写入,此时文件已不存在)。解决方案:
- 撤销时按命令执行的逆序操作(用栈存储已执行命令,撤销时弹出栈顶命令);
- 命令执行前记录完整的上下文快照(比如文件的原始内容、数据库的事务日志),撤销时直接恢复快照,而非反向执行操作。
5.3 命令与接收者耦合过紧
坑:命令类直接依赖接收者的具体实现(比如BackupCommand依赖BackupExecutor的backupFile方法),当接收者方法修改时,命令类也需要修改。解决方案:
- 给接收者定义抽象接口(如
IBackupExecutor),命令类依赖抽象接口而非具体实现; - 采用依赖注入模式,将接收者注入命令类,而非命令类直接创建接收者。
5.4 忽略命令的幂等性
坑:命令重复执行导致业务异常(比如重复备份文件导致磁盘满、重复扣库存导致数据错误)。解决方案:
- 保证命令的幂等性:同一命令执行多次,结果与执行一次一致(比如备份文件时先判断目标文件是否存在,存在则跳过);
- 给命令添加唯一标识(UUID),执行前检查该命令是否已执行过(比如存在数据库中),避免重复执行。
5.5 调用者与命令耦合过紧
坑:调用者直接依赖具体命令类(比如遥控器绑定LightOnCommand),新增命令时需要修改调用者代码。解决方案:
- 调用者依赖抽象命令类,通过配置文件或工厂模式动态创建命令对象(比如配置文件中指定 “按钮 0 绑定 LightOnCommand”,调用者读取配置创建命令);
- 用策略模式 + 命令模式结合:将命令的创建逻辑封装到工厂类
4257

被折叠的 条评论
为什么被折叠?



