1. 命令模式基础回顾
命令模式是GoF 23种设计模式中最具实用性的行为型模式之一。它通过将请求封装为对象,使得我们可以参数化客户端请求、排队或记录请求,并支持可撤销操作。在C++中,命令模式通常由四个核心组件构成:
- Command:抽象命令接口,声明执行操作的接口
- ConcreteCommand:具体命令实现,绑定接收者与动作
- Invoker:触发命令执行的调用者
- Receiver:知道如何执行请求的实际操作对象
传统实现示例如下:
cpp复制// 抽象命令
class Command {
public:
virtual ~Command() = default;
virtual void execute() = 0;
};
// 具体命令
class ConcreteCommand : public Command {
Receiver* receiver_;
public:
explicit ConcreteCommand(Receiver* receiver) : receiver_(receiver) {}
void execute() override { receiver_->action(); }
};
// 接收者
class Receiver {
public:
void action() { /* 具体业务逻辑 */ }
};
// 调用者
class Invoker {
Command* command_;
public:
void setCommand(Command* command) { command_ = command; }
void executeCommand() { command_->execute(); }
};
这种基础实现虽然清晰,但在实际工程中往往会遇到各种限制。接下来我们将探讨几种实用的变体实现方案。
2. 模板命令模式
2.1 静态多态实现
传统命令模式通过虚函数实现运行时多态,但在性能敏感场景下,我们可以使用模板实现编译期多态:
cpp复制template<typename Receiver>
class TemplateCommand {
Receiver* receiver_;
using Action = void(Receiver::*)();
Action action_;
public:
TemplateCommand(Receiver* receiver, Action action)
: receiver_(receiver), action_(action) {}
void execute() { (receiver_->*action_)(); }
};
// 使用示例
struct CustomReceiver {
void operation1() { /*...*/ }
void operation2() { /*...*/ }
};
CustomReceiver receiver;
TemplateCommand<CustomReceiver> cmd1(&receiver, &CustomReceiver::operation1);
TemplateCommand<CustomReceiver> cmd2(&receiver, &CustomReceiver::operation2);
提示:这种实现完全消除了虚函数开销,适合高频调用的命令场景。但会损失部分灵活性,因为命令类型在编译期就已确定。
2.2 带参数的模板命令
扩展模板命令以支持参数传递:
cpp复制template<typename Receiver, typename... Args>
class ParametricCommand {
Receiver* receiver_;
using Action = void(Receiver::*)(Args...);
Action action_;
std::tuple<Args...> args_;
public:
ParametricCommand(Receiver* receiver, Action action, Args... args)
: receiver_(receiver), action_(action), args_(std::forward<Args>(args)...) {}
void execute() {
std::apply([this](auto&&... args) {
(receiver_->*action_)(std::forward<decltype(args)>(args)...);
}, args_);
}
};
// 使用示例
struct AdvancedReceiver {
void process(int id, const std::string& name) {
std::cout << "Processing " << id << ":" << name << "\n";
}
};
AdvancedReceiver ar;
ParametricCommand cmd(&ar, &AdvancedReceiver::process, 42, "example");
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();
}
}
};
// 使用示例
MacroCommand macro;
macro.addCommand(std::make_unique<ConcreteCommand>(&receiver1));
macro.addCommand(std::make_unique<ConcreteCommand>(&receiver2));
macro.execute();
3.2 事务性命令
增强宏命令以支持原子性操作:
cpp复制class TransactionCommand : public Command {
std::vector<std::unique_ptr<Command>> commands_;
std::vector<std::function<void()>> rollbacks_;
public:
template<typename Cmd, typename Rollback>
void addCommand(std::unique_ptr<Cmd> cmd, Rollback&& rb) {
commands_.push_back(std::move(cmd));
rollbacks_.emplace_back(std::forward<Rollback>(rb));
}
void execute() override {
try {
for (auto& cmd : commands_) {
cmd->execute();
}
} catch (...) {
for (auto it = rollbacks_.rbegin(); it != rollbacks_.rend(); ++it) {
(*it)();
}
throw;
}
}
};
4. 异步命令模式
4.1 基于future/promise的实现
cpp复制class AsyncCommand : public Command {
std::promise<void> promise_;
std::function<void()> task_;
public:
explicit AsyncCommand(std::function<void()> task) : task_(std::move(task)) {}
std::future<void> getFuture() { return promise_.get_future(); }
void execute() override {
try {
task_();
promise_.set_value();
} catch (...) {
promise_.set_exception(std::current_exception());
}
}
};
// 使用示例
AsyncCommand asyncCmd([]{
std::this_thread::sleep_for(1s);
std::cout << "Async task completed\n";
});
auto future = asyncCmd.getFuture();
std::thread t([&asyncCmd] { asyncCmd.execute(); });
future.wait();
t.join();
4.2 线程池集成
cpp复制class ThreadPoolCommand : public Command {
static ThreadPool pool_;
std::function<void()> task_;
public:
explicit ThreadPoolCommand(std::function<void()> task) : task_(std::move(task)) {}
void execute() override {
pool_.enqueue(task_);
}
};
// 使用示例
ThreadPoolCommand poolCmd([]{
// 长时间运行的任务
});
poolCmd.execute(); // 非阻塞执行
5. 可撤销命令模式
5.1 基础撤销实现
cpp复制class UndoableCommand : public Command {
protected:
Receiver* receiver_;
int previous_state_;
public:
explicit UndoableCommand(Receiver* receiver) : receiver_(receiver) {}
void execute() override {
previous_state_ = receiver_->getState();
receiver_->action();
}
virtual void undo() {
receiver_->setState(previous_state_);
}
};
5.2 多级撤销栈
cpp复制class CommandManager {
std::stack<std::unique_ptr<UndoableCommand>> undo_stack_;
std::stack<std::unique_ptr<UndoableCommand>> redo_stack_;
public:
void executeCommand(std::unique_ptr<UndoableCommand> cmd) {
cmd->execute();
undo_stack_.push(std::move(cmd));
redo_stack_ = {}; // 清空重做栈
}
void undo() {
if (undo_stack_.empty()) return;
auto cmd = std::move(undo_stack_.top());
undo_stack_.pop();
cmd->undo();
redo_stack_.push(std::move(cmd));
}
void redo() {
if (redo_stack_.empty()) return;
auto cmd = std::move(redo_stack_.top());
redo_stack_.pop();
cmd->execute();
undo_stack_.push(std::move(cmd));
}
};
6. 类型擦除命令模式
6.1 使用std::function实现
cpp复制class AnyCommand {
std::function<void()> action_;
public:
template<typename Callable>
AnyCommand(Callable&& callable)
: action_(std::forward<Callable>(callable)) {}
void execute() { action_(); }
};
// 使用示例
AnyCommand cmd1([] { std::cout << "Lambda command\n"; });
AnyCommand cmd2(std::bind(&Receiver::action, &receiver));
6.2 带类型擦除的参数传递
cpp复制class ParametricAnyCommand {
std::function<void()> action_;
public:
template<typename Callable, typename... Args>
ParametricAnyCommand(Callable&& callable, Args&&... args) {
action_ = [=] { std::invoke(callable, args...); };
}
void execute() { action_(); }
};
// 使用示例
ParametricAnyCommand cmd(&Receiver::action, &receiver);
7. 性能优化技巧
7.1 命令对象池
cpp复制class CommandPool {
std::vector<std::unique_ptr<Command>> pool_;
public:
template<typename Cmd, typename... Args>
Command* acquire(Args&&... args) {
if (pool_.empty()) {
return new Cmd(std::forward<Args>(args)...);
}
auto cmd = pool_.back().release();
pool_.pop_back();
return cmd;
}
void release(Command* cmd) {
pool_.emplace_back(cmd);
}
};
// 使用示例
CommandPool pool;
auto cmd = pool.acquire<ConcreteCommand>(&receiver);
cmd->execute();
pool.release(cmd);
7.2 小型命令优化
对于极简命令,可使用lambda直接存储操作:
cpp复制class LightweightCommand {
std::function<void()> action_;
public:
template<typename F>
explicit LightweightCommand(F&& f) : action_(std::forward<F>(f)) {}
void execute() { action_(); }
};
// 使用示例
LightweightCommand lc([]{
// 简单操作
});
8. 实际应用案例
8.1 GUI命令处理
cpp复制class Button {
std::unique_ptr<Command> command_;
public:
void setCommand(std::unique_ptr<Command> cmd) {
command_ = std::move(cmd);
}
void onClick() {
if (command_) command_->execute();
}
};
// 使用示例
auto button = std::make_unique<Button>();
button->setCommand(std::make_unique<ConcreteCommand>(&receiver));
8.2 游戏输入系统
cpp复制class InputHandler {
std::unordered_map<Key, std::unique_ptr<Command>> key_bindings_;
public:
void bindKey(Key key, std::unique_ptr<Command> cmd) {
key_bindings_[key] = std::move(cmd);
}
void handleInput() {
for (const auto& [key, cmd] : key_bindings_) {
if (isKeyPressed(key)) {
cmd->execute();
}
}
}
};
9. 测试与调试建议
9.1 命令日志记录
cpp复制class LoggingCommand : public Command {
Command* wrapped_;
std::ostream& log_;
public:
LoggingCommand(Command* cmd, std::ostream& log = std::clog)
: wrapped_(cmd), log_(log) {}
void execute() override {
log_ << "Executing command at " << std::chrono::system_clock::now() << "\n";
wrapped_->execute();
}
};
9.2 命令耗时统计
cpp复制class ProfilingCommand : public Command {
Command* wrapped_;
public:
explicit ProfilingCommand(Command* cmd) : wrapped_(cmd) {}
void execute() override {
auto start = std::chrono::high_resolution_clock::now();
wrapped_->execute();
auto end = std::chrono::high_resolution_clock::now();
std::cout << "Command took "
<< std::chrono::duration_cast<std::chrono::microseconds>(end-start).count()
<< "μs\n";
}
};
10. 设计考量与取舍
在选择命令模式变体时,需要考虑以下因素:
| 考量因素 | 传统实现 | 模板命令 | 类型擦除 | 异步命令 |
|---|---|---|---|---|
| 运行时开销 | 高(虚函数) | 低 | 中等 | 取决于实现 |
| 灵活性 | 高 | 低 | 高 | 高 |
| 可维护性 | 高 | 中 | 高 | 中 |
| 线程安全 | 容易 | 容易 | 容易 | 需要额外处理 |
| 适用场景 | 通用 | 性能关键 | 回调密集 | 长时间任务 |
在实际项目中,我通常会根据以下原则选择实现方式:
- 对于核心高频命令,优先考虑模板命令以获得最佳性能
- 需要动态配置命令时,使用类型擦除或传统虚函数实现
- IO密集型操作采用异步命令避免阻塞
- 用户交互场景确保实现完善的撤销/重做功能
- 内存敏感环境考虑使用命令对象池
命令模式最大的优势在于将操作的具体实现与调用者解耦。通过选择合适的变体,可以在不损失灵活性的前提下获得更好的性能或更简洁的实现。