1. 命令模式:解耦请求与执行的利器
在C++开发中,我们经常遇到这样的场景:某个对象需要执行一系列操作,但这些操作的具体内容、执行时机或执行者可能随时变化。比如游戏中的角色技能系统、GUI的撤销/重做功能、任务队列管理等。传统做法是用一堆if-else或switch-case硬编码,导致代码臃肿且难以维护。这就是命令模式要解决的核心问题。
命令模式将"请求"封装成独立的对象,使你可以参数化客户端与具体执行逻辑。举个例子,想象餐厅点餐场景:顾客(调用者)不需要知道厨师(接收者)如何烹饪牛排,只需将订单(命令对象)交给服务员即可。这种间接性带来了惊人的灵活性——你可以排队命令、记录命令历史、实现撤销/重做,甚至在不同线程间传递命令。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 命令模式的核心结构解析
2.1 UML类图与角色分工
典型的命令模式包含以下核心组件(以C++实现为例):
cpp复制// 抽象命令接口
class Command {
public:
virtual ~Command() = default;
virtual void execute() = 0;
virtual void undo() = 0; // 可选的反向操作
};
// 具体命令
class ConcreteCommand : public Command {
public:
ConcreteCommand(Receiver* receiver) : receiver_(receiver) {}
void execute() override {
receiver_->action(params); // 委托给接收者执行
}
void undo() override {
receiver_->reverseAction(params); // 撤销操作
}
private:
Receiver* receiver_;
// 命令参数...
};
// 接收者(真正执行业务逻辑的对象)
class Receiver {
public:
void action(/* params */) { /* 具体实现 */ }
void reverseAction(/* params */) { /* 撤销实现 */ }
};
// 调用者(触发命令的对象)
class Invoker {
public:
void setCommand(Command* cmd) { command_ = cmd; }
void executeCommand() { command_->execute(); }
private:
Command* command_;
};
2.2 各组件协作流程
- 客户端创建
Receiver和ConcreteCommand对象,并将接收者绑定到命令 - Invoker持有命令对象,通过
executeCommand()触发执行 - ConcreteCommand调用
Receiver的具体方法完成操作 - 如需撤销,
Invoker可调用命令的undo()方法
关键点:Invoker完全不知道Receiver的存在,实现了完全的解耦。这也是该模式被称为"好莱坞原则"(Don't call us, we'll call you)的典型体现。
3. C++实现中的关键技巧
3.1 智能指针管理生命周期
原始指针在命令模式中容易引发内存泄漏。推荐使用std::unique_ptr:
cpp复制class Invoker {
public:
void setCommand(std::unique_ptr<Command> cmd) {
command_ = std::move(cmd);
}
// ...
private:
std::unique_ptr<Command> command_;
};
// 使用示例
auto receiver = std::make_shared<Receiver>();
auto cmd = std::make_unique<ConcreteCommand>(receiver.get());
invoker.setCommand(std::move(cmd));
3.2 支持可变参数的命令
利用C++11的可变参数模板实现通用命令:
cpp复制template<typename Receiver, typename... Args>
class GenericCommand : public Command {
public:
using Action = void (Receiver::*)(Args...);
GenericCommand(Receiver* r, Action a, Args... args)
: receiver_(r), action_(a), args_(std::make_tuple(args...)) {}
void execute() override {
std::apply([this](auto&&... args) {
(receiver_->*action_)(std::forward<decltype(args)>(args)...);
}, args_);
}
private:
Receiver* receiver_;
Action action_;
std::tuple<Args...> args_;
};
// 使用示例
editor->addCommand(std::make_unique<GenericCommand>(
textBuffer, &TextBuffer::insertText, pos, "Hello"));
3.3 实现无限级撤销/重做
通过命令历史栈实现:
cpp复制class CommandHistory {
public:
void push(std::unique_ptr<Command> cmd) {
undoStack_.push(std::move(cmd));
// 清空redo栈(新命令使旧redo无效)
while (!redoStack_.empty()) redoStack_.pop();
}
void undo() {
if (undoStack_.empty()) return;
auto cmd = std::move(undoStack_.top());
undoStack_.pop();
cmd->undo();
redoStack_.push(std::move(cmd));
}
void redo() {
if (redoStack_.empty()) return;
auto cmd = std::move(redoStack_.top());
redoStack_.pop();
cmd->execute();
undoStack_.push(std::move(cmd));
}
private:
std::stack<std::unique_ptr<Command>> undoStack_;
std::stack<std::unique_ptr<Command>> redoStack_;
};
4. 实战案例:文本编辑器设计
4.1 基础命令实现
cpp复制// 接收者:文本缓冲区
class TextBuffer {
public:
void insert(size_t pos, const std::string& text) {
content_.insert(pos, text);
lastEditPos_ = pos;
}
void erase(size_t pos, size_t len) {
deleted_ = content_.substr(pos, len);
content_.erase(pos, len);
lastEditPos_ = pos;
}
const std::string& getContent() const { return content_; }
private:
std::string content_;
std::string deleted_; // 记录删除内容用于undo
size_t lastEditPos_;
};
// 插入命令
class InsertCommand : public Command {
public:
InsertCommand(TextBuffer* buffer, size_t pos, const std::string& text)
: buffer_(buffer), pos_(pos), text_(text) {}
void execute() override { buffer_->insert(pos_, text_); }
void undo() override {
buffer_->erase(pos_, text_.length());
}
private:
TextBuffer* buffer_;
size_t pos_;
std::string text_;
};
// 删除命令
class DeleteCommand : public Command {
public:
DeleteCommand(TextBuffer* buffer, size_t pos, size_t len)
: buffer_(buffer), pos_(pos), len_(len) {}
void execute() override { buffer_->erase(pos_, len_); }
void undo() override {
buffer_->insert(pos_, deleted_);
}
private:
TextBuffer* buffer_;
size_t pos_;
size_t len_;
std::string deleted_;
};
4.2 复合命令(宏命令)
cpp复制class MacroCommand : public Command {
public:
void addCommand(std::unique_ptr<Command> cmd) {
commands_.push_back(std::move(cmd));
}
void execute() override {
for (auto& cmd : commands_) {
cmd->execute();
}
}
void undo() override {
for (auto it = commands_.rbegin(); it != commands_.rend(); ++it) {
(*it)->undo();
}
}
private:
std::vector<std::unique_ptr<Command>> commands_;
};
// 使用示例:批量替换功能
auto macro = std::make_unique<MacroCommand>();
macro->addCommand(std::make_unique<DeleteCommand>(buffer, 10, 5));
macro->addCommand(std::make_unique<InsertCommand>(buffer, 10, "new"));
invoker.setCommand(std::move(macro));
5. 性能优化与陷阱规避
5.1 命令对象池化
频繁创建/销毁命令对象可能引发性能问题。可采用对象池模式:
cpp复制template<typename T>
class CommandPool {
public:
template<typename... Args>
std::unique_ptr<T, std::function<void(T*)>> acquire(Args&&... args) {
if (pool_.empty()) {
return {
new T(std::forward<Args>(args)...),
[this](T* p) { pool_.push_back(std::unique_ptr<T>(p)); }
};
}
auto ptr = std::move(pool_.back());
pool_.pop_back();
ptr->reset(std::forward<Args>(args)...); // 假设T有reset方法
return {
ptr.release(),
[this](T* p) { pool_.push_back(std::unique_ptr<T>(p)); }
};
}
private:
std::vector<std::unique_ptr<T>> pool_;
};
// 在具体命令类中添加reset方法
class InsertCommand : public Command {
public:
void reset(TextBuffer* buffer, size_t pos, const std::string& text) {
buffer_ = buffer;
pos_ = pos;
text_ = text;
}
// ...其他实现...
};
5.2 线程安全实现
当命令需要在多线程环境执行时:
cpp复制class ThreadSafeInvoker {
public:
void asyncExecute(std::unique_ptr<Command> cmd) {
std::lock_guard<std::mutex> lock(mutex_);
queue_.push(std::move(cmd));
cv_.notify_one();
}
void startWorker() {
worker_ = std::thread([this] {
while (running_) {
std::unique_ptr<Command> cmd;
{
std::unique_lock<std::mutex> lock(mutex_);
cv_.wait(lock, [this] { return !queue_.empty() || !running_; });
if (!running_) break;
cmd = std::move(queue_.front());
queue_.pop();
}
cmd->execute();
}
});
}
void stop() {
running_ = false;
cv_.notify_all();
if (worker_.joinable()) worker_.join();
}
private:
std::queue<std::unique_ptr<Command>> queue_;
std::mutex mutex_;
std::condition_variable cv_;
std::thread worker_;
bool running_ = true;
};
5.3 常见陷阱与解决方案
-
循环引用问题:当命令持有接收者的shared_ptr,而接收者又间接持有命令时,会导致内存泄漏。解决方案:
- 优先使用原始指针或weak_ptr跨组件引用
- 明确生命周期所有权关系
-
不可逆命令:不是所有操作都能完美undo。应对策略:
- 设计快照机制(如备忘录模式)
- 对不可逆操作提供明确提示
-
命令膨胀:当命令类过多时,可采用:
- 参数化命令(如前述GenericCommand)
- 原型模式克隆命令模板
6. 现代C++中的演进实现
6.1 使用std::function替代接口
C++11后可以用函数对象简化命令模式:
cpp复制class FunctionCommand {
public:
using ExecuteFunc = std::function<void()>;
using UndoFunc = std::function<void()>;
FunctionCommand(ExecuteFunc exe, UndoFunc undo = {})
: execute_(std::move(exe)), undo_(std::move(undo)) {}
void execute() { if (execute_) execute_(); }
void undo() { if (undo_) undo_(); }
private:
ExecuteFunc execute_;
UndoFunc undo_;
};
// 使用示例
TextBuffer buffer;
auto cmd = std::make_unique<FunctionCommand>(
[&] { buffer.insert(0, "Hello"); },
[&] { buffer.erase(0, 5); }
);
6.2 结合lambda表达式
直接传递lambda给调用者:
cpp复制class LambdaInvoker {
public:
template<typename F>
void setCommand(F&& f) {
command_ = std::forward<F>(f);
}
void executeCommand() { command_(); }
private:
std::function<void()> command_;
};
// 使用示例
LambdaInvoker invoker;
std::string result;
invoker.setCommand([&] {
result = "Executed at " + std::to_string(time(nullptr));
});
invoker.executeCommand();
6.3 编译期命令模式
通过模板元编程实现零成本抽象:
cpp复制template<typename Receiver, typename Action, typename... Args>
class CompileTimeCommand {
public:
constexpr CompileTimeCommand(Receiver& r, Action a, Args... args)
: receiver_(r), action_(a), args_(args...) {}
void execute() const {
std::apply([this](auto&&... args) {
(receiver_.*action_)(std::forward<decltype(args)>(args)...);
}, args_);
}
private:
Receiver& receiver_;
Action action_;
std::tuple<Args...> args_;
};
// 使用示例
struct Light {
void turnOn(int brightness) { /*...*/ }
};
Light light;
auto cmd = CompileTimeCommand(&Light::turnOn, light, 50);
cmd.execute();
7. 设计模式组合实战
7.1 命令+工厂模式
创建命令对象的更优雅方式:
cpp复制class CommandFactory {
public:
static std::unique_ptr<Command> createInsertCommand(
TextBuffer* buffer, size_t pos, const std::string& text)
{
return std::make_unique<InsertCommand>(buffer, pos, text);
}
template<typename... Args>
static std::unique_ptr<Command> create(Args&&... args) {
return std::make_unique<std::decay_t<Args>...>(
std::forward<Args>(args)...);
}
};
// 使用示例
auto cmd = CommandFactory::create<InsertCommand>(buffer, 0, "Prefix");
7.2 命令+责任链模式
实现可替换的命令处理器:
cpp复制class CommandHandler {
public:
virtual ~CommandHandler() = default;
void setNext(std::shared_ptr<CommandHandler> next) { next_ = next; }
virtual bool handle(Command& cmd) {
if (next_) return next_->handle(cmd);
return false;
}
protected:
std::shared_ptr<CommandHandler> next_;
};
class LoggingHandler : public CommandHandler {
public:
bool handle(Command& cmd) override {
std::cout << "Executing command\n";
return CommandHandler::handle(cmd);
}
};
class UndoableHandler : public CommandHandler {
public:
bool handle(Command& cmd) override {
history_.push(cmd);
return CommandHandler::handle(cmd);
}
void undoLast() {
if (!history_.empty()) {
history_.top().undo();
history_.pop();
}
}
private:
std::stack<std::reference_wrapper<Command>> history_;
};
7.3 命令+观察者模式
实现命令执行通知:
cpp复制class CommandNotifier {
public:
using Listener = std::function<void(const Command&)>;
void addListener(Listener l) {
listeners_.push_back(std::move(l));
}
void notify(const Command& cmd) {
for (auto& l : listeners_) {
l(cmd);
}
}
private:
std::vector<Listener> listeners_;
};
class ObservableCommand : public Command {
public:
ObservableCommand(std::unique_ptr<Command> cmd, CommandNotifier& notifier)
: cmd_(std::move(cmd)), notifier_(notifier) {}
void execute() override {
cmd_->execute();
notifier_.notify(*cmd_);
}
void undo() override {
cmd_->undo();
notifier_.notify(*cmd_);
}
private:
std::unique_ptr<Command> cmd_;
CommandNotifier& notifier_;
};
8. 测试策略与调试技巧
8.1 单元测试命令对象
使用Google Test框架示例:
cpp复制TEST(CommandTest, InsertCommandExecution) {
TextBuffer buffer;
InsertCommand cmd(&buffer, 0, "Test");
cmd.execute();
EXPECT_EQ(buffer.getContent(), "Test");
cmd.undo();
EXPECT_TRUE(buffer.getContent().empty());
}
TEST(CommandTest, MacroCommandOrder) {
TextBuffer buffer;
auto macro = std::make_unique<MacroCommand>();
macro->addCommand(std::make_unique<InsertCommand>(&buffer, 0, "A"));
macro->addCommand(std::make_unique<InsertCommand>(&buffer, 1, "B"));
macro->execute();
EXPECT_EQ(buffer.getContent(), "AB");
macro->undo();
EXPECT_TRUE(buffer.getContent().empty());
}
8.2 使用Mock对象测试
cpp复制class MockReceiver {
public:
MOCK_METHOD(void, action, (int param), ());
MOCK_METHOD(void, reverseAction, (int param), ());
};
TEST(CommandTest, DelegatesToReceiver) {
MockReceiver receiver;
EXPECT_CALL(receiver, action(42)).Times(1);
EXPECT_CALL(receiver, reverseAction(42)).Times(1);
ConcreteCommand cmd(&receiver, 42);
cmd.execute();
cmd.undo();
}
8.3 调试命令队列的技巧
-
打印命令历史:
cpp复制void printHistory(const CommandHistory& hist) { auto stack = hist.getUndoStack(); // 假设提供访问方法 while (!stack.empty()) { std::cout << typeid(*stack.top()).name() << "\n"; stack.pop(); } } -
断点设置策略:
- 在Command::execute()和undo()虚函数处设断点
- 使用条件断点过滤特定命令类型
-
日志记录:
cpp复制class LoggingCommand : public Command { public: LoggingCommand(std::unique_ptr<Command> cmd) : cmd_(std::move(cmd)) {} void execute() override { std::cout << "Executing: " << typeid(*cmd_).name() << "\n"; cmd_->execute(); } void undo() override { std::cout << "Undoing: " << typeid(*cmd_).name() << "\n"; cmd_->undo(); } private: std::unique_ptr<Command> cmd_; };
9. 实际工程应用案例
9.1 游戏开发中的输入处理
cpp复制class InputHandler {
public:
std::unique_ptr<Command> handleInput() {
if (isPressed(BUTTON_X)) return std::make_unique<JumpCommand>();
if (isPressed(BUTTON_Y)) return std::make_unique<FireCommand>();
if (isPressed(BUTTON_A)) return std::make_unique<SwapWeaponCommand>();
return nullptr;
}
};
// 游戏循环中
auto cmd = inputHandler.handleInput();
if (cmd) {
cmd->execute();
commandHistory.push(std::move(cmd));
}
9.2 GUI系统中的撤销/重做
cpp复制class Document {
public:
void insertText(size_t pos, const std::string& text) {
auto cmd = std::make_unique<InsertCommand>(&buffer_, pos, text);
cmd->execute();
history_.push(std::move(cmd));
}
void undo() { history_.undo(); }
void redo() { history_.redo(); }
private:
TextBuffer buffer_;
CommandHistory history_;
};
9.3 网络请求队列
cpp复制class RequestScheduler {
public:
void schedule(std::unique_ptr<Command> request) {
pending_.push(std::move(request));
}
void processNext() {
if (pending_.empty()) return;
auto cmd = std::move(pending_.front());
pending_.pop();
try {
cmd->execute();
completed_.push(std::move(cmd));
} catch (const std::exception& e) {
failed_.push(std::move(cmd));
}
}
private:
std::queue<std::unique_ptr<Command>> pending_;
std::queue<std::unique_ptr<Command>> completed_;
std::queue<std::unique_ptr<Command>> failed_;
};
10. 性能对比与模式选择
10.1 命令模式 vs 直接调用
| 维度 | 命令模式 | 直接调用 |
|---|---|---|
| 耦合度 | 低(完全解耦) | 高(直接依赖) |
| 灵活性 | 高(可排队、撤销、记录等) | 低(即时执行) |
| 内存开销 | 较高(每个命令都是对象) | 低(无额外对象) |
| 适用场景 | 需要控制执行流程的复杂系统 | 简单直接的调用 |
10.2 命令模式变体对比
| 变体 | 优点 | 缺点 |
|---|---|---|
| 传统接口实现 | 类型安全,明确职责 | 类数量膨胀 |
| std::function实现 | 简洁,减少类定义 | 缺乏统一接口 |
| 模板命令 | 编译期优化,零成本抽象 | 代码复杂度高 |
| 异步命令 | 非阻塞执行 | 需要处理线程安全 |
10.3 何时选择命令模式
- 需要撤销/重做功能:如编辑器、绘图软件等
- 需要排队或延迟执行:如任务调度系统
- 需要记录操作历史:如审计日志系统
- 需要高扩展性:如插件架构中动态加载命令
- 需要事务行为:要么全部成功,要么全部回滚
经验法则:当发现自己在不断添加新的条件分支来处理不同操作时,就是考虑命令模式的时机。
