1. 为什么我们需要命令模式
在C++开发中,我们经常会遇到这样的场景:用户点击按钮后需要执行一系列复杂操作,或者需要支持操作的撤销/重做功能。传统做法是直接在按钮回调中编写业务逻辑,但这种紧耦合的设计会带来几个明显问题:
- 业务逻辑与界面控制强绑定,难以复用
- 无法记录操作历史
- 难以实现异步执行
- 新增操作需要修改现有代码
命令模式通过将"请求"封装为独立对象,完美解决了这些问题。我在一个图形编辑器项目中就深刻体会到了它的价值——当产品经理第5次要求修改撤销逻辑时,命令模式让我只需调整一个类就完成了变更。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 命令模式的核心结构
2.1 基础类设计
典型的命令模式包含以下核心组件:
cpp复制class Command {
public:
virtual ~Command() = default;
virtual void execute() = 0;
virtual void undo() = 0;
};
class Invoker {
std::vector<std::unique_ptr<Command>> history_;
public:
void executeCommand(std::unique_ptr<Command> cmd) {
cmd->execute();
history_.push_back(std::move(cmd));
}
void undo() {
if (!history_.empty()) {
history_.back()->undo();
history_.pop_back();
}
}
};
2.2 具体命令实现
以文档编辑器为例,实现一个插入文本的命令:
cpp复制class InsertTextCommand : public Command {
Document& doc_;
size_t pos_;
std::string text_;
bool executed_{false};
public:
InsertTextCommand(Document& doc, size_t pos, std::string text)
: doc_(doc), pos_(pos), text_(std::move(text)) {}
void execute() override {
if (!executed_) {
doc_.insert(pos_, text_);
executed_ = true;
}
}
void undo() override {
if (executed_) {
doc_.erase(pos_, text_.length());
executed_ = false;
}
}
};
关键点:命令对象应该保持幂等性,即多次执行产生相同结果。这是实现可靠撤销/重做的前提。
3. 高级应用技巧
3.1 复合命令模式
当需要将多个命令作为一个原子操作时:
cpp复制class MacroCommand : public Command {
std::vector<std::unique_ptr<Command>> commands_;
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();
}
}
};
3.2 异步命令执行
结合C++11的异步特性:
cpp复制class AsyncCommand : public Command {
std::future<void> future_;
std::function<void()> task_;
public:
explicit AsyncCommand(std::function<void()> task)
: task_(std::move(task)) {}
void execute() override {
future_ = std::async(std::launch::async, task_);
}
void undo() override {
// 异步命令通常不可撤销
throw std::runtime_error("Async commands cannot be undone");
}
void wait() { future_.wait(); }
};
4. 实战中的性能优化
4.1 命令对象池
频繁创建/销毁命令对象可能导致性能问题,可采用对象池模式:
cpp复制class CommandPool {
std::unordered_map<std::type_index, std::vector<std::unique_ptr<Command>>> pool_;
public:
template<typename T, typename... Args>
T* acquire(Args&&... args) {
auto& pool = pool_[typeid(T)];
if (pool.empty()) {
return new T(std::forward<Args>(args)...);
}
auto ptr = dynamic_cast<T*>(pool.back().release());
pool.pop_back();
new (ptr) T(std::forward<Args>(args)...);
return ptr;
}
template<typename T>
void release(T* cmd) {
cmd->~T();
pool_[typeid(T)].emplace_back(cmd);
}
};
4.2 轻量级命令模式
对于简单命令,可使用lambda简化:
cpp复制class LambdaCommand : public Command {
std::function<void()> execute_;
std::function<void()> undo_;
public:
LambdaCommand(std::function<void()> execute,
std::function<void()> undo)
: execute_(std::move(execute))
, undo_(std::move(undo)) {}
void execute() override { execute_(); }
void undo() override { undo_(); }
};
// 使用示例
invoker.executeCommand(
std::make_unique<LambdaCommand>(
[]{ std::cout << "Do something"; },
[]{ std::cout << "Undo something"; }
)
);
5. 设计考量与陷阱规避
5.1 命令的生命周期管理
在长期运行的系统中,命令对象可能占用大量内存。建议:
- 对不可撤销的命令及时释放
- 设置历史记录的最大长度
- 对大型数据使用共享指针
cpp复制class History {
std::deque<std::shared_ptr<Command>> commands_;
size_t max_size_{100};
public:
void add(std::shared_ptr<Command> cmd) {
commands_.push_back(cmd);
if (commands_.size() > max_size_) {
commands_.pop_front();
}
}
};
5.2 线程安全问题
当命令涉及共享资源时:
cpp复制class ThreadSafeCommand : public Command {
std::mutex mtx_;
Resource& resource_;
public:
void execute() override {
std::lock_guard<std::mutex> lock(mtx_);
resource_.modify();
}
void undo() override {
std::lock_guard<std::mutex> lock(mtx_);
resource_.restore();
}
};
6. 现代C++特性应用
6.1 使用std::function替代接口
C++11后可以更灵活地实现命令模式:
cpp复制using CommandFunc = std::function<void()>;
class ModernCommand {
CommandFunc execute_;
CommandFunc undo_;
public:
ModernCommand(CommandFunc execute, CommandFunc undo)
: execute_(std::move(execute))
, undo_(std::move(undo)) {}
void execute() { execute_(); }
void undo() { undo_(); }
};
6.2 可变参数模板支持
创建通用的命令工厂:
cpp复制template<typename T>
class CommandFactory {
public:
template<typename... Args>
static std::unique_ptr<Command> create(Args&&... args) {
return std::make_unique<T>(std::forward<Args>(args)...);
}
};
// 使用示例
auto cmd = CommandFactory<InsertTextCommand>::create(doc, 0, "Hello");
7. 实际项目集成案例
7.1 游戏开发中的应用
在游戏引擎中,命令模式常用于:
- 用户输入处理
- 关卡编辑器操作
- 回放系统实现
cpp复制class MoveUnitCommand : public Command {
GameUnit& unit_;
Position from_;
Position to_;
public:
MoveUnitCommand(GameUnit& unit, Position to)
: unit_(unit), from_(unit.position()), to_(to) {}
void execute() override {
unit_.moveTo(to_);
}
void undo() override {
unit_.moveTo(from_);
}
};
7.2 GUI框架集成
与Qt/MFC等框架结合时:
cpp复制class QtCommand : public QObject, public Command {
Q_OBJECT
public:
void execute() override {
QMetaObject::invokeMethod(this, "onExecute", Qt::QueuedConnection);
}
protected slots:
virtual void onExecute() = 0;
};
class SaveFileCommand : public QtCommand {
QString filename_;
QByteArray content_;
protected:
void onExecute() override {
QFile file(filename_);
file.open(QIODevice::WriteOnly);
file.write(content_);
}
};
8. 测试与调试策略
8.1 单元测试要点
测试命令对象时应关注:
- 执行后的状态变化
- 撤销后的状态恢复
- 多次执行的幂等性
cpp复制TEST(CommandTest, InsertTextUndoRestoresState) {
Document doc;
auto cmd = std::make_unique<InsertTextCommand>(doc, 0, "test");
cmd->execute();
ASSERT_EQ(doc.content(), "test");
cmd->undo();
ASSERT_TRUE(doc.content().empty());
}
8.2 日志记录策略
为调试复杂命令流,可添加日志:
cpp复制class LoggingCommand : public Command {
Command& wrapped_;
std::ostream& log_;
public:
void execute() override {
log_ << "Executing command...";
wrapped_.execute();
log_ << "Command executed";
}
void undo() override {
log_ << "Undoing command...";
wrapped_.undo();
log_ << "Command undone";
}
};
9. 扩展与变体模式
9.1 事务性命令模式
确保一组命令要么全部成功,要么全部回滚:
cpp复制class Transaction {
std::vector<std::unique_ptr<Command>> commands_;
public:
void add(std::unique_ptr<Command> cmd) {
commands_.push_back(std::move(cmd));
}
bool commit() {
try {
for (auto& cmd : commands_) {
cmd->execute();
}
return true;
} catch (...) {
rollback();
return false;
}
}
void rollback() {
for (auto it = commands_.rbegin(); it != commands_.rend(); ++it) {
(*it)->undo();
}
}
};
9.2 命令模式与状态模式结合
实现更复杂的状态管理:
cpp复制class StatefulCommand : public Command {
Context& context_;
State* prevState_;
public:
void execute() override {
prevState_ = context_.currentState();
context_.changeState(new NewState);
}
void undo() override {
context_.changeState(prevState_);
}
};
10. 性能对比与选型建议
10.1 内存开销对比
不同实现方式的性能特点:
| 实现方式 | 内存占用 | 执行速度 | 适用场景 |
|---|---|---|---|
| 传统虚函数 | 高 | 中 | 需要多态 |
| std::function | 中 | 中 | 简单命令 |
| Lambda表达式 | 低 | 高 | 临时命令 |
10.2 设计决策树
根据项目需求选择合适变体:
- 需要撤销功能?→ 基础命令模式
- 命令数量很多?→ 考虑对象池
- 需要异步执行?→ 异步命令变体
- 需要事务支持?→ 事务性命令
- 需要组合操作?→ 复合命令模式
在最近的一个高性能交易系统项目中,我们最终选择了基于对象池和lambda的混合方案,在保证功能完整性的同时将命令处理延迟降低了40%。
