1. 命令模式:解耦请求与执行的利器
在C++开发中,我们经常遇到这样的场景:某个对象需要执行请求,但又不确定具体由谁执行、何时执行。比如GUI中的按钮点击、游戏中的技能释放、事务管理中的操作回滚等。命令模式(Command Pattern)正是为解决这类问题而生。
我第一次真正理解命令模式的价值,是在开发一个图形编辑器时。当时需要实现撤销/重做功能,如果直接在按钮点击事件里调用具体绘图操作,代码会变得极其混乱。命令模式通过将"请求"封装成独立对象,完美解决了这个问题。它不仅让代码更清晰,还意外地带来了许多扩展可能性。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 命令模式的核心结构解析
2.1 经典UML类图实现
标准的命令模式包含以下几个关键角色:
code复制Client -> Invoker
Invoker -> Command
Command <|-- ConcreteCommand
ConcreteCommand -> Receiver
在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();
}
void undo() override {
receiver_->reverseAction();
}
private:
Receiver* receiver_;
};
// 调用者
class Invoker {
public:
void setCommand(Command* cmd) {
command_ = cmd;
}
void executeCommand() {
command_->execute();
}
private:
Command* command_;
};
2.2 现代C++的改进实现
随着C++11/14/17的演进,我们可以用更现代的方式实现命令模式:
cpp复制#include <functional>
#include <memory>
using Command = std::function<void()>;
class ModernInvoker {
public:
void setCommand(Command cmd) {
command_ = std::move(cmd);
}
void executeCommand() {
if(command_) command_();
}
private:
Command command_;
};
这种实现利用了std::function和lambda表达式,代码更加简洁。但要注意,这种简化版可能不适合需要复杂撤销操作的场景。
3. 命令模式的五种典型应用场景
3.1 图形编辑器中的撤销/重做
这是命令模式最经典的应用。每个编辑操作(如添加形状、修改颜色)都被封装为一个命令对象:
cpp复制class AddShapeCommand : public Command {
public:
AddShapeCommand(Document& doc, ShapeType type)
: doc_(doc), type_(type) {}
void execute() override {
addedShape_ = doc_.addShape(type_);
}
void undo() override {
doc_.removeShape(addedShape_);
}
private:
Document& doc_;
ShapeType type_;
Shape* addedShape_ = nullptr;
};
通过维护命令历史栈,实现撤销/重做变得非常简单:
cpp复制std::stack<std::unique_ptr<Command>> history;
// 执行新命令
auto cmd = std::make_unique<AddShapeCommand>(doc, ShapeType::Circle);
cmd->execute();
history.push(std::move(cmd));
// 撤销
if(!history.empty()) {
history.top()->undo();
history.pop();
}
3.2 游戏开发中的输入处理
在游戏引擎中,命令模式可以优雅地处理玩家输入:
cpp复制class JumpCommand : public Command {
public:
explicit JumpCommand(Character& character)
: character_(character) {}
void execute() override {
character_.jump();
}
// 跳跃通常不需要撤销
void undo() override {}
private:
Character& character_;
};
// 按键映射
std::unordered_map<KeyCode, std::unique_ptr<Command>> keyBindings;
keyBindings[KEY_SPACE] = std::make_unique<JumpCommand>(player);
// 处理输入
void handleInput(KeyCode key) {
if(auto it = keyBindings.find(key); it != keyBindings.end()) {
it->second->execute();
}
}
这种设计使得重新映射按键、实现回放系统等需求变得非常简单。
3.3 异步任务队列
命令模式非常适合实现任务队列系统:
cpp复制class TaskQueue {
public:
void addTask(std::unique_ptr<Command> task) {
std::lock_guard<std::mutex> lock(mutex_);
tasks_.push(std::move(task));
}
void processTasks() {
while(!tasks_.empty()) {
auto task = std::move(tasks_.front());
tasks_.pop();
task->execute();
}
}
private:
std::queue<std::unique_ptr<Command>> tasks_;
std::mutex mutex_;
};
3.4 事务系统
在需要原子性操作的系统中,命令模式可以确保操作要么全部成功,要么全部回滚:
cpp复制class Transaction {
public:
void addCommand(std::unique_ptr<Command> cmd) {
commands_.push_back(std::move(cmd));
}
bool commit() {
for(auto& cmd : commands_) {
try {
cmd->execute();
} catch(...) {
rollback();
return false;
}
}
return true;
}
void rollback() {
for(auto it = commands_.rbegin(); it != commands_.rend(); ++it) {
(*it)->undo();
}
}
private:
std::vector<std::unique_ptr<Command>> commands_;
};
3.5 菜单系统
GUI框架中的菜单项通常使用命令模式实现:
cpp复制class MenuItem {
public:
MenuItem(const std::string& label, std::unique_ptr<Command> cmd)
: label_(label), command_(std::move(cmd)) {}
void onClick() {
command_->execute();
}
private:
std::string label_;
std::unique_ptr<Command> command_;
};
4. 命令模式的进阶技巧与优化
4.1 命令组合(宏命令)
通过组合多个命令,可以创建复杂操作:
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_;
};
4.2 延迟执行与命令队列
对于耗时操作,可以实现延迟执行:
cpp复制class DelayedCommand : public Command {
public:
DelayedCommand(std::unique_ptr<Command> cmd, std::chrono::milliseconds delay)
: command_(std::move(cmd)), delay_(delay) {}
void execute() override {
std::this_thread::sleep_for(delay_);
command_->execute();
}
void undo() override {
command_->undo();
}
private:
std::unique_ptr<Command> command_;
std::chrono::milliseconds delay_;
};
4.3 命令池与对象复用
频繁创建/销毁命令对象时,可以使用对象池优化性能:
cpp复制template <typename T>
class CommandPool {
public:
template <typename... Args>
std::unique_ptr<T, std::function<void(T*)>> acquire(Args&&... args) {
std::unique_ptr<T, std::function<void(T*)>> ptr(
pool_.empty() ? new T(std::forward<Args>(args)...) : pool_.back().release(),
[this](T* cmd) { pool_.push_back(std::unique_ptr<T>(cmd)); }
);
if(!pool_.empty()) pool_.pop_back();
return ptr;
}
private:
std::vector<std::unique_ptr<T>> pool_;
};
4.4 序列化与网络传输
将命令序列化后可以在网络上传输,实现远程命令执行:
cpp复制class NetworkCommand : public Command {
public:
virtual std::vector<uint8_t> serialize() const = 0;
static std::unique_ptr<NetworkCommand> deserialize(const std::vector<uint8_t>& data);
};
class MoveUnitCommand : public NetworkCommand {
public:
MoveUnitCommand(UnitId unit, Position to) : unit_(unit), to_(to) {}
void execute() override {
// 实际移动逻辑
}
std::vector<uint8_t> serialize() const override {
std::vector<uint8_t> data(sizeof(UnitId) + sizeof(Position));
// 序列化实现...
return data;
}
private:
UnitId unit_;
Position to_;
};
5. 命令模式与其他设计模式的协作
5.1 与备忘录模式结合
备忘录模式可以帮助命令模式实现更复杂的撤销机制:
cpp复制class Memento {
public:
virtual ~Memento() = default;
};
class EditorMemento : public Memento {
// 保存编辑器状态
};
class Editor {
public:
std::unique_ptr<Memento> createMemento() const {
return std::make_unique<EditorMemento>(/* 当前状态 */);
}
void restoreFromMemento(const Memento* memento) {
// 恢复状态
}
};
class ModifyCommand : public Command {
public:
ModifyCommand(Editor& editor) : editor_(editor) {
beforeState_ = editor.createMemento();
}
void execute() override {
// 执行修改
}
void undo() override {
editor_.restoreFromMemento(beforeState_.get());
}
private:
Editor& editor_;
std::unique_ptr<Memento> beforeState_;
};
5.2 与责任链模式结合
将命令传递给责任链处理:
cpp复制class CommandHandler {
public:
virtual ~CommandHandler() = default;
virtual bool handle(Command& cmd) = 0;
void setNext(std::unique_ptr<CommandHandler> next) {
next_ = std::move(next);
}
protected:
std::unique_ptr<CommandHandler> next_;
};
class LoggingHandler : public CommandHandler {
public:
bool handle(Command& cmd) override {
// 记录日志
if(next_) return next_->handle(cmd);
return false;
}
};
class CommandProcessor {
public:
void process(Command& cmd) {
if(chain_) chain_->handle(cmd);
}
void addHandler(std::unique_ptr<CommandHandler> handler) {
if(!chain_) {
chain_ = std::move(handler);
} else {
auto current = chain_.get();
while(current->next_) current = current->next_.get();
current->setNext(std::move(handler));
}
}
private:
std::unique_ptr<CommandHandler> chain_;
};
5.3 与原型模式结合
通过原型模式克隆命令:
cpp复制class CloneableCommand : public Command {
public:
virtual std::unique_ptr<CloneableCommand> clone() const = 0;
};
class ConcreteCloneableCommand : public CloneableCommand {
public:
std::unique_ptr<CloneableCommand> clone() const override {
return std::make_unique<ConcreteCloneableCommand>(*this);
}
void execute() override { /* ... */ }
void undo() override { /* ... */ }
};
6. 命令模式的性能考量与最佳实践
6.1 内存管理策略
命令对象的内存管理需要特别注意:
-
智能指针的使用:
cpp复制// 推荐使用unique_ptr管理命令生命周期 std::unique_ptr<Command> cmd = std::make_unique<ConcreteCommand>(receiver); // 如果需要共享所有权,使用shared_ptr std::shared_ptr<Command> sharedCmd = std::make_shared<ConcreteCommand>(receiver); -
自定义内存分配器:
对于高频创建/销毁的命令对象,可以考虑使用内存池:cpp复制template <typename T> class CommandAllocator { public: T* allocate() { if(freeList_) { auto ptr = freeList_; freeList_ = freeList_->next; return ptr; } return new T(); } void deallocate(T* ptr) { ptr->next = freeList_; freeList_ = ptr; } private: union Node { T value; Node* next; }; Node* freeList_ = nullptr; };
6.2 线程安全实现
在多线程环境中使用命令模式时:
cpp复制class ThreadSafeInvoker {
public:
void setCommand(std::unique_ptr<Command> cmd) {
std::lock_guard<std::mutex> lock(mutex_);
command_ = std::move(cmd);
}
void executeCommand() {
std::unique_ptr<Command> localCmd;
{
std::lock_guard<std::mutex> lock(mutex_);
if(command_) localCmd = std::move(command_);
}
if(localCmd) localCmd->execute();
}
private:
std::mutex mutex_;
std::unique_ptr<Command> command_;
};
6.3 日志与调试
为命令添加日志功能有助于调试:
cpp复制class LoggingCommand : public Command {
public:
LoggingCommand(std::unique_ptr<Command> cmd, Logger& logger)
: wrapped_(std::move(cmd)), logger_(logger) {}
void execute() override {
logger_.log("Executing command");
wrapped_->execute();
}
void undo() override {
logger_.log("Undoing command");
wrapped_->undo();
}
private:
std::unique_ptr<Command> wrapped_;
Logger& logger_;
};
6.4 实际项目中的经验教训
-
避免过度设计:不是所有操作都需要命令模式,简单场景直接调用函数可能更合适。
-
命令粒度控制:命令太细会导致数量爆炸,太粗会降低灵活性。通常一个用户操作对应一个命令比较合适。
-
内存消耗监控:长时间运行的系统要注意命令历史的内存占用,可能需要限制历史记录数量。
-
序列化版本控制:如果需要持久化命令,要为命令实现版本控制,以便后续兼容性处理。
-
测试策略:命令对象应该易于单元测试,每个命令应该只做一件事,且做好输入验证。
