1. 命令模式基础与实战价值
命令模式是行为型设计模式中最具工程实用性的模式之一。在C++这种强类型静态语言中,命令模式通过将操作抽象为对象,解决了传统回调机制的诸多痛点。我曾在多个大型C++项目中运用命令模式重构代码,最典型的案例是一个跨平台GUI框架的撤销/重做系统,通过命令对象封装操作,使历史记录管理变得异常简单。
命令模式的核心在于解耦请求发送者与接收者。想象餐厅点餐场景:顾客(发送者)不需要知道厨师(接收者)如何烹饪,只需将订单(命令对象)交给服务员。这种间接调用的特性,使得命令模式特别适合以下场景:
- 需要支持撤销/重做功能(如编辑器操作)
- 需要实现操作队列或任务调度
- 需要支持事务性操作(要么全部成功,要么全部失败)
- 需要为不同条件配置不同操作(如UI按钮行为)
2. 模式结构深度解析
2.1 经典UML类图实现
标准的命令模式包含五个关键角色:
Command(抽象命令)
- 声明执行操作的接口(通常为
execute()) - 示例代码:
class Command { public: virtual ~Command() = default; virtual void execute() const = 0; virtual void undo() const = 0; // 撤销操作扩展 };
- 声明执行操作的接口(通常为
ConcreteCommand(具体命令)
- 实现Command接口
- 持有接收者引用并调用其方法
- 示例:
class CopyCommand : public Command { Document* receiver; // 接收者 public: explicit CopyCommand(Document* doc) : receiver(doc) {} void execute() const override { receiver->copySelection(); } void undo() const override { receiver->deleteSelection(); } };
Invoker(调用者)
- 触发命令执行
- 不直接依赖具体接收者
- 典型实现:
class MenuItem { Command* command; public: void setCommand(Command* cmd) { command = cmd; } void click() { if (command) command->execute(); } };
Receiver(接收者)
- 知道如何执行实际操作
- 业务逻辑的真正实现者
- 示例:
class Document { public: void copySelection() { // 实际复制逻辑 cout << "Text copied to clipboard\n"; } };
Client(客户端)
- 创建具体命令并设置接收者
- 配置调用者与命令的关联
2.2 现代C++实现变体
随着C++标准演进,我们可以用更现代的方式实现命令模式:
使用std::function的轻量级实现
class Invoker { std::function<void()> command; public: void setCommand(std::function<void()> cmd) { command = cmd; } void execute() { if (command) command(); } }; // 使用示例 Document doc; Invoker invoker; invoker.setCommand([&doc]{ doc.copySelection(); });支持智能指针的线程安全版本
using CommandPtr = std::shared_ptr<Command>; class ThreadSafeInvoker { std::mutex mtx; std::vector<CommandPtr> commandQueue; public: void addCommand(CommandPtr cmd) { std::lock_guard<std::mutex> lock(mtx); commandQueue.push_back(cmd); } void executeAll() { std::lock_guard<std::mutex> lock(mtx); for (auto& cmd : commandQueue) { cmd->execute(); } commandQueue.clear(); } };3. 实战案例:编辑器命令系统
让我们通过一个完整的文本编辑器案例,展示命令模式的实际应用。这个编辑器需要支持以下功能:
- 文本插入/删除
- 格式修改(粗体/斜体)
- 无限级撤销/重做
- 宏命令(组合命令)
3.1 基础命令实现
首先定义编辑器核心类和基础命令:
class Editor { string text; vector<string> clipboard; public: void insertText(size_t pos, const string& newText) { text.insert(pos, newText); } void deleteText(size_t pos, size_t len) { text.erase(pos, len); } void copyToClipboard(size_t start, size_t end) { string selected = text.substr(start, end-start); clipboard.push_back(selected); } string getText() const { return text; } }; class InsertCommand : public Command { Editor* editor; size_t position; string text; public: InsertCommand(Editor* ed, size_t pos, const string& txt) : editor(ed), position(pos), text(txt) {} void execute() const override { editor->insertText(position, text); } void undo() const override { editor->deleteText(position, text.length()); } };3.2 撤销系统实现
实现命令历史管理器支持撤销/重做:
class CommandHistory { vector<unique_ptr<Command>> history; vector<unique_ptr<Command>> redoStack; public: void execute(unique_ptr<Command> cmd) { cmd->execute(); history.push_back(std::move(cmd)); redoStack.clear(); // 新命令使重做栈失效 } void undo() { if (history.empty()) return; auto cmd = std::move(history.back()); history.pop_back(); cmd->undo(); redoStack.push_back(std::move(cmd)); } void redo() { if (redoStack.empty()) return; auto cmd = std::move(redoStack.back()); redoStack.pop_back(); cmd->execute(); history.push_back(std::move(cmd)); } };3.3 宏命令实现
组合多个命令形成复合操作:
class MacroCommand : public Command { vector<unique_ptr<Command>> commands; public: void addCommand(unique_ptr<Command> cmd) { commands.push_back(std::move(cmd)); } void execute() const override { for (const auto& cmd : commands) { cmd->execute(); } } void undo() const override { for (auto it = commands.rbegin(); it != commands.rend(); ++it) { (*it)->undo(); } } }; // 使用示例 Editor editor; auto macro = make_unique<MacroCommand>(); macro->addCommand(make_unique<InsertCommand>(&editor, 0, "Hello")); macro->addCommand(make_unique<InsertCommand>(&editor, 5, " World")); CommandHistory history; history.execute(std::move(macro)); // 文本变为 "Hello World" history.undo(); // 文本清空4. 高级应用与性能优化
4.1 命令池模式
频繁创建/销毁命令对象时,可采用对象池优化:
class CommandPool { static const size_t POOL_SIZE = 100; array<InsertCommand, POOL_SIZE> insertPool; // 其他命令类型的池... size_t insertIndex = 0; public: Command* acquireInsertCommand(Editor* ed, size_t pos, const string& txt) { if (insertIndex >= POOL_SIZE) return nullptr; auto cmd = &insertPool[insertIndex++]; new (cmd) InsertCommand(ed, pos, txt); return cmd; } void releaseAll() { insertIndex = 0; // 其他池的索引重置... } };4.2 异步命令执行
结合C++多线程实现异步命令:
class AsyncCommand : public Command { Command* wrapped; promise<void> completion; public: explicit AsyncCommand(Command* cmd) : wrapped(cmd) {} future<void> getFuture() { return completion.get_future(); } void execute() const override { thread([this] { wrapped->execute(); completion.set_value(); }).detach(); } }; // 使用示例 Editor editor; auto cmd = new InsertCommand(&editor, 0, "Async text"); AsyncCommand asyncCmd(cmd); auto fut = asyncCmd.getFuture(); CommandHistory history; history.execute(unique_ptr<Command>(&asyncCmd)); fut.wait(); // 等待异步操作完成4.3 命令序列化
支持网络传输或持久化的命令序列化:
class SerializableCommand : public Command { public: virtual string serialize() const = 0; static unique_ptr<Command> deserialize(const string& data); }; class NetworkInvoker { queue<string> commandQueue; mutex queueMutex; Editor* editor; public: explicit NetworkInvoker(Editor* ed) : editor(ed) {} void receiveCommand(const string& data) { lock_guard<mutex> lock(queueMutex); commandQueue.push(data); } void processCommands() { unique_lock<mutex> lock(queueMutex); while (!commandQueue.empty()) { auto data = commandQueue.front(); commandQueue.pop(); lock.unlock(); auto cmd = SerializableCommand::deserialize(data); cmd->execute(); lock.lock(); } } };5. 常见问题与调试技巧
5.1 内存管理陷阱
命令模式常见的内存问题及解决方案:
问题1:命令对象生命周期管理
错误示例:在未完成异步操作时释放命令对象
解决方案:
// 使用shared_ptr管理命令生命周期 auto cmd = make_shared<InsertCommand>(editor, 0, "Text"); asyncExecute([cmd] { cmd->execute(); });问题2:接收者提前销毁
错误示例:命令持有已销毁的接收者指针
解决方案:
// 使用weak_ptr检测接收者是否有效 class SafeCommand : public Command { weak_ptr<Editor> editor; public: explicit SafeCommand(shared_ptr<Editor> ed) : editor(ed) {} void execute() const override { if (auto ed = editor.lock()) { ed->insertText(0, "Safe"); } } };5.2 多线程同步问题
竞态条件场景:
- 多个线程同时修改命令历史
- 命令执行期间接收者状态改变
线程安全改造方案:
class ThreadSafeHistory { mutex mtx; vector<shared_ptr<Command>> history; public: void execute(shared_ptr<Command> cmd) { lock_guard<mutex> lock(mtx); cmd->execute(); history.push_back(cmd); } bool undo() { lock_guard<mutex> lock(mtx); if (history.empty()) return false; auto cmd = history.back(); cmd->undo(); history.pop_back(); return true; } };5.3 调试日志增强
为命令添加可追溯的调试信息:
class LoggedCommand : public Command { Command* wrapped; string name; public: LoggedCommand(Command* cmd, string cmdName) : wrapped(cmd), name(std::move(cmdName)) {} void execute() const override { cout << "[CMD] Executing: " << name << endl; auto start = chrono::high_resolution_clock::now(); wrapped->execute(); auto end = chrono::high_resolution_clock::now(); auto duration = chrono::duration_cast<chrono::microseconds>(end-start); cout << "[CMD] Completed in " << duration.count() << "μs\n"; } }; // 使用示例 auto cmd = new InsertCommand(editor, 0, "Text"); auto loggedCmd = new LoggedCommand(cmd, "InsertText");6. 模式扩展与替代方案
6.1 与其它模式的协作
命令模式 + 组合模式:
- 创建宏命令(组合多个子命令)
- 实现命令的树形结构
命令模式 + 备忘录模式:
- 存储命令执行前的状态
- 实现更精确的撤销操作
命令模式 + 原型模式:
- 通过克隆快速创建相似命令
- 减少命令对象的构造开销
6.2 替代方案比较
函数指针 vs 命令对象
- 函数指针更轻量但不支持状态保存
- 命令对象更灵活但内存开销较大
观察者模式 vs 命令模式
- 观察者:一对多通知,松散耦合
- 命令:封装操作请求,支持撤销
策略模式 vs 命令模式
- 策略:算法替换,通常无状态
- 命令:操作封装,包含完整上下文
在实际项目中,我经常遇到需要权衡这些模式的情况。根据经验,当遇到以下需求时命令模式是最佳选择:
- 需要操作队列或日志
- 需要支持撤销/重做
- 需要延迟执行操作
- 需要将操作作为参数传递
7. 现代C++的最佳实践
7.1 使用lambda表达式
现代C++中,lambda可以简化命令实现:
class LambdaCommand : public Command { function<void()> executeFunc; function<void()> undoFunc; public: LambdaCommand(function<void()> exec, function<void()> und) : executeFunc(exec), undoFunc(und) {} void execute() const override { if (executeFunc) executeFunc(); } void undo() const override { if (undoFunc) undoFunc(); } }; // 使用示例 Editor editor; auto cmd = make_unique<LambdaCommand>( [&] { editor.insertText(0, "Lambda"); }, [&] { editor.deleteText(0, 6); } );7.2 可变参数模板命令
支持任意参数的命令工厂:
template <typename Receiver, typename... Args> class GenericCommand : public Command { Receiver* receiver; void (Receiver::*action)(Args...); tuple<Args...> args; public: GenericCommand(Receiver* rec, void (Receiver::*act)(Args...), Args... a) : receiver(rec), action(act), args(a...) {} void execute() const override { apply([this](auto&&... args) { (receiver->*action)(forward<decltype(args)>(args)...); }, args); } }; // 使用示例 auto cmd = new GenericCommand(&editor, &Editor::insertText, 0, "Generic");7.3 基于概念的命令约束
C++20概念可以约束命令类型:
template <typename T> concept CommandConcept = requires(T cmd) { { cmd.execute() } -> same_as<void>; { cmd.undo() } -> same_as<void>; }; template <CommandConcept Cmd> class CommandProcessor { vector<Cmd> history; public: void execute(Cmd cmd) { cmd.execute(); history.push_back(cmd); } };在大型C++项目中实施命令模式时,我建议从简单场景开始,逐步扩展到复杂用例。初期可以先用std::function实现基础命令,随着需求复杂化再引入完整的类层次结构。性能关键路径要注意命令对象的创建开销,考虑使用对象池或缓存优化。