1. 命令模式基础概念回顾
命令模式是面向对象设计中最具威力的行为型模式之一。它的核心思想是将请求封装成对象,从而允许用户使用不同的请求、队列或日志来参数化其他对象。在C++中实现命令模式时,我们通常会定义一个抽象Command基类,包含纯虚的execute()方法,然后派生出各种具体命令类。
典型的命令模式结构包含以下角色:
- Invoker(调用者):负责调用命令对象执行请求
- Command(命令):声明执行操作的接口
- ConcreteCommand(具体命令):将接收者对象与动作绑定
- Receiver(接收者):知道如何执行与请求相关的操作
- Client(客户端):创建具体命令对象并设置接收者
cpp复制class Command {
public:
virtual ~Command() = default;
virtual void execute() = 0;
};
class Light {
public:
void turnOn() { /* 具体实现 */ }
void turnOff() { /* 具体实现 */ }
};
class LightOnCommand : public Command {
Light& light_;
public:
explicit LightOnCommand(Light& light) : light_(light) {}
void execute() override { light_.turnOn(); }
};
// 使用示例
Light livingRoomLight;
auto cmd = std::make_unique<LightOnCommand>(livingRoomLight);
cmd->execute();
2. 命令模式的常见变体实现
2.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<LightOnCommand>(light1));
macro.addCommand(std::make_unique<LightOffCommand>(light2));
macro.execute();
提示:复合命令特别适合需要实现"撤销/重做"功能的场景,可以将一系列操作作为一个原子单元来处理。
2.2 带撤销功能的命令
通过扩展Command接口增加undo()方法,可以实现命令的撤销功能。这需要命令对象保存执行前的状态。
cpp复制class UndoableCommand : public Command {
public:
virtual void undo() = 0;
};
class LightToggleCommand : public UndoableCommand {
Light& light_;
bool prevState_;
public:
explicit LightToggleCommand(Light& light) : light_(light) {}
void execute() override {
prevState_ = light_.isOn();
light_.toggle();
}
void undo() override {
if (prevState_ != light_.isOn()) {
light_.toggle();
}
}
};
2.3 异步命令
将命令执行放到单独的线程中,适用于耗时操作。需要注意线程安全问题。
cpp复制class AsyncCommand : public Command {
std::unique_ptr<Command> cmd_;
std::future<void> future_;
public:
explicit AsyncCommand(std::unique_ptr<Command> cmd)
: cmd_(std::move(cmd)) {}
void execute() override {
future_ = std::async(std::launch::async, [this] {
cmd_->execute();
});
}
void wait() { if (future_.valid()) future_.wait(); }
};
3. 命令模式的高级应用技巧
3.1 命令队列与调度
命令队列是实现任务调度系统的常见方式。我们可以创建一个命令队列,由专门的调度线程按顺序执行。
cpp复制class CommandQueue {
std::queue<std::unique_ptr<Command>> queue_;
std::mutex mutex_;
std::condition_variable cv_;
bool stop_ = false;
public:
void addCommand(std::unique_ptr<Command> cmd) {
std::lock_guard<std::mutex> lock(mutex_);
queue_.push(std::move(cmd));
cv_.notify_one();
}
void run() {
while (true) {
std::unique_ptr<Command> cmd;
{
std::unique_lock<std::mutex> lock(mutex_);
cv_.wait(lock, [this] { return !queue_.empty() || stop_; });
if (stop_) break;
cmd = std::move(queue_.front());
queue_.pop();
}
cmd->execute();
}
}
void stop() {
std::lock_guard<std::mutex> lock(mutex_);
stop_ = true;
cv_.notify_one();
}
};
3.2 命令工厂与依赖注入
使用工厂模式创建命令对象,可以解耦命令的创建和使用。
cpp复制class CommandFactory {
public:
virtual std::unique_ptr<Command> createLightOnCommand(Light&) = 0;
virtual std::unique_ptr<Command> createLightOffCommand(Light&) = 0;
// 其他命令创建方法...
};
class DefaultCommandFactory : public CommandFactory {
public:
std::unique_ptr<Command> createLightOnCommand(Light& light) override {
return std::make_unique<LightOnCommand>(light);
}
// 其他实现...
};
3.3 基于策略的命令执行
将命令执行策略与命令本身分离,可以实现更灵活的执行控制。
cpp复制class ExecutionStrategy {
public:
virtual void execute(Command& cmd) = 0;
};
class ImmediateExecution : public ExecutionStrategy {
public:
void execute(Command& cmd) override { cmd.execute(); }
};
class LoggedExecution : public ExecutionStrategy {
std::ostream& log_;
public:
explicit LoggedExecution(std::ostream& log) : log_(log) {}
void execute(Command& cmd) override {
log_ << "Executing command..." << std::endl;
cmd.execute();
log_ << "Command executed." << std::endl;
}
};
4. 命令模式在C++中的性能优化
4.1 内存管理优化
频繁创建销毁命令对象可能导致内存碎片。可以使用对象池模式优化。
cpp复制template <typename T>
class CommandPool {
std::vector<std::unique_ptr<T>> pool_;
public:
template <typename... Args>
T* acquire(Args&&... args) {
if (pool_.empty()) {
pool_.push_back(std::make_unique<T>(std::forward<Args>(args)...));
}
auto ptr = pool_.back().get();
pool_.pop_back();
return ptr;
}
void release(std::unique_ptr<T> cmd) {
pool_.push_back(std::move(cmd));
}
};
4.2 命令的轻量化实现
对于简单命令,可以使用函数指针或std::function替代完整的命令类层次。
cpp复制using SimpleCommand = std::function<void()>;
class FunctionCommand : public Command {
SimpleCommand fn_;
public:
explicit FunctionCommand(SimpleCommand fn) : fn_(std::move(fn)) {}
void execute() override { fn_(); }
};
// 使用示例
Light light;
auto cmd = std::make_unique<FunctionCommand>([&light] { light.turnOn(); });
4.3 编译时命令模式
使用模板和CRTP可以在编译时实现命令模式,避免运行时多态开销。
cpp复制template <typename Derived>
class StaticCommand {
public:
void execute() {
static_cast<Derived*>(this)->executeImpl();
}
};
class StaticLightOnCommand : public StaticCommand<StaticLightOnCommand> {
Light& light_;
public:
explicit StaticLightOnCommand(Light& light) : light_(light) {}
void executeImpl() { light_.turnOn(); }
};
5. 命令模式在实际项目中的应用案例
5.1 GUI系统中的菜单命令
在图形界面应用中,菜单项通常对应各种命令。使用命令模式可以轻松实现菜单功能的动态配置。
cpp复制// 菜单项类
class MenuItem {
std::unique_ptr<Command> command_;
public:
void setCommand(std::unique_ptr<Command> cmd) {
command_ = std::move(cmd);
}
void onClick() {
if (command_) command_->execute();
}
};
// 使用示例
auto saveCmd = std::make_unique<SaveDocumentCommand>(document);
menuItem.setCommand(std::move(saveCmd));
5.2 游戏开发中的输入处理
游戏中的按键绑定可以很好地使用命令模式实现,支持按键重映射等功能。
cpp复制class InputHandler {
std::unordered_map<KeyCode, std::unique_ptr<Command>> keyBindings_;
public:
void bindKey(KeyCode key, std::unique_ptr<Command> cmd) {
keyBindings_[key] = std::move(cmd);
}
void handleInput(KeyCode key) {
auto it = keyBindings_.find(key);
if (it != keyBindings_.end()) {
it->second->execute();
}
}
};
5.3 事务处理系统
需要支持回滚的业务系统可以使用命令模式实现原子性操作。
cpp复制class Transaction {
std::vector<std::unique_ptr<UndoableCommand>> commands_;
public:
void addCommand(std::unique_ptr<UndoableCommand> cmd) {
commands_.push_back(std::move(cmd));
}
void commit() {
for (auto& cmd : commands_) {
cmd->execute();
}
}
void rollback() {
for (auto it = commands_.rbegin(); it != commands_.rend(); ++it) {
(*it)->undo();
}
}
};
6. 命令模式的最佳实践与陷阱
6.1 何时使用命令模式
命令模式特别适用于以下场景:
- 需要将操作请求者和执行者解耦
- 需要支持撤销/重做功能
- 需要支持事务性操作
- 需要将操作排队或记录操作日志
- 需要支持宏命令或批处理
6.2 常见实现陷阱
-
内存泄漏:忘记删除命令对象。建议使用智能指针管理命令生命周期。
-
线程安全问题:多个线程访问同一命令对象。确保命令要么是无状态的,要么有适当的同步机制。
-
过度设计:对于简单操作,直接使用函数指针或lambda可能更合适。
-
性能问题:大量小命令对象可能导致内存碎片。考虑使用对象池优化。
6.3 测试策略
命令模式组件的测试要点:
- 测试命令是否正确地调用了接收者的方法
- 测试复合命令是否按正确顺序执行子命令
- 测试撤销功能是否正确地恢复了状态
- 测试异步命令是否正确地处理了并发
cpp复制TEST(CommandTest, LightOnCommandTurnsOnLight) {
MockLight light;
EXPECT_CALL(light, turnOn()).Times(1);
LightOnCommand cmd(light);
cmd.execute();
}
7. C++20/23中的现代命令模式实现
7.1 使用协程实现异步命令
C++20引入的协程可以简化异步命令的实现。
cpp复制struct AsyncCommand {
struct promise_type {
AsyncCommand get_return_object() { return {}; }
std::suspend_never initial_suspend() { return {}; }
std::suspend_never final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() { std::terminate(); }
};
};
AsyncCommand asyncOperation() {
co_await std::suspend_always{};
// 异步操作实现...
}
7.2 使用概念约束命令类型
C++20概念可以用于约束命令类型。
cpp复制template <typename T>
concept CommandType = requires(T cmd) {
{ cmd.execute() } -> std::same_as<void>;
};
template <CommandType Cmd>
void executeCommand(Cmd& cmd) {
cmd.execute();
}
7.3 使用span处理命令参数
C++20的span可以安全地传递命令参数。
cpp复制class ParameterizedCommand : public Command {
std::span<const int> params_;
public:
explicit ParameterizedCommand(std::span<const int> params)
: params_(params) {}
void execute() override {
for (int param : params_) {
// 处理参数...
}
}
};
8. 命令模式与其他模式的结合
8.1 命令模式与备忘录模式
结合备忘录模式可以实现更强大的撤销功能。
cpp复制class Document {
std::string content_;
public:
class Memento {
friend class Document;
std::string content_;
Memento(const std::string& content) : content_(content) {}
};
Memento createMemento() const { return Memento(content_); }
void restoreFromMemento(const Memento& m) { content_ = m.content_; }
// 其他方法...
};
class ChangeDocumentCommand : public UndoableCommand {
Document& doc_;
Document::Memento memento_;
std::string newContent_;
public:
ChangeDocumentCommand(Document& doc, std::string content)
: doc_(doc), newContent_(std::move(content)) {}
void execute() override {
memento_ = doc_.createMemento();
doc_.setContent(newContent_);
}
void undo() override {
doc_.restoreFromMemento(memento_);
}
};
8.2 命令模式与责任链模式
将命令传递给责任链可以创建灵活的处理流程。
cpp复制class CommandHandler {
CommandHandler* next_ = nullptr;
public:
void setNext(CommandHandler* next) { next_ = next; }
virtual bool handle(Command& cmd) {
if (next_) return next_->handle(cmd);
return false;
}
};
class LoggingHandler : public CommandHandler {
public:
bool handle(Command& cmd) override {
std::cout << "Executing command..." << std::endl;
bool result = CommandHandler::handle(cmd);
std::cout << "Command executed." << std::endl;
return result;
}
};
8.3 命令模式与访问者模式
使用访问者模式可以实现在命令上的双重分发。
cpp复制class CommandVisitor {
public:
virtual void visit(LightOnCommand&) = 0;
virtual void visit(LightOffCommand&) = 0;
// 其他命令访问方法...
};
class Command {
public:
virtual void accept(CommandVisitor& visitor) = 0;
// 其他方法...
};
class LightOnCommand : public Command {
public:
void accept(CommandVisitor& visitor) override {
visitor.visit(*this);
}
// 其他实现...
};
9. 命令模式在嵌入式系统中的应用
9.1 内存受限环境下的优化
在资源受限的嵌入式系统中,可以使用静态分配和命令共享来优化内存使用。
cpp复制template <size_t MaxCommands>
class StaticCommandPool {
std::array<Command*, MaxCommands> pool_;
size_t count_ = 0;
public:
bool addCommand(Command* cmd) {
if (count_ >= MaxCommands) return false;
pool_[count_++] = cmd;
return true;
}
void executeAll() {
for (size_t i = 0; i < count_; ++i) {
pool_[i]->execute();
}
}
};
9.2 硬件抽象层的命令封装
使用命令模式封装硬件操作可以提高代码的可测试性和可移植性。
cpp复制class GPIOCommand : public Command {
enum class Action { SetHigh, SetLow, Toggle };
GPIO_Pin pin_;
Action action_;
public:
GPIOCommand(GPIO_Pin pin, Action action)
: pin_(pin), action_(action) {}
void execute() override {
switch (action_) {
case Action::SetHigh: HAL_GPIO_WritePin(pin_, GPIO_PIN_SET); break;
case Action::SetLow: HAL_GPIO_WritePin(pin_, GPIO_PIN_RESET); break;
case Action::Toggle: HAL_GPIO_TogglePin(pin_); break;
}
}
};
9.3 实时系统中的命令优先级
在实时系统中,可以为命令添加优先级属性,实现优先级调度。
cpp复制class PrioritizedCommand : public Command {
Command& cmd_;
int priority_;
public:
PrioritizedCommand(Command& cmd, int priority)
: cmd_(cmd), priority_(priority) {}
void execute() override { cmd_.execute(); }
int priority() const { return priority_; }
};
class PriorityCommandQueue {
std::priority_queue<
std::reference_wrapper<PrioritizedCommand>,
std::vector<std::reference_wrapper<PrioritizedCommand>>,
decltype([](auto& a, auto& b) {
return a.get().priority() < b.get().priority();
})
> queue_;
public:
void addCommand(PrioritizedCommand& cmd) {
queue_.push(cmd);
}
void executeNext() {
if (!queue_.empty()) {
auto cmd = queue_.top();
queue_.pop();
cmd.get().execute();
}
}
};
10. 命令模式的扩展与未来演进
10.1 分布式命令模式
在分布式系统中,命令可以被序列化并在不同节点间传输执行。
cpp复制class SerializableCommand : public Command {
public:
virtual std::string serialize() const = 0;
static std::unique_ptr<SerializableCommand> deserialize(const std::string&);
};
class RemoteCommandExecutor {
NetworkClient& client_;
public:
void executeRemote(std::unique_ptr<SerializableCommand> cmd) {
auto serialized = cmd->serialize();
client_.send(serialized);
}
};
10.2 基于事件的命令触发
将命令与事件系统集成,实现事件驱动的命令执行。
cpp复制class EventDispatcher {
std::unordered_map<EventType, std::unique_ptr<Command>> handlers_;
public:
void registerHandler(EventType type, std::unique_ptr<Command> cmd) {
handlers_[type] = std::move(cmd);
}
void dispatch(EventType type) {
auto it = handlers_.find(type);
if (it != handlers_.end()) {
it->second->execute();
}
}
};
10.3 机器学习驱动的命令生成
结合机器学习模型,可以动态生成适合当前上下文的命令。
cpp复制class MLCommandGenerator {
MLModel& model_;
public:
std::unique_ptr<Command> generateCommand(const Context& context) {
auto prediction = model_.predict(context);
// 根据预测结果创建适当的命令
return createCommandFromPrediction(prediction);
}
};
在实际C++项目中应用命令模式时,我发现最关键的决策点在于确定命令的粒度。命令太细会导致大量小对象,增加系统复杂性;命令太粗又会降低灵活性。经过多次实践,我总结出一个经验法则:如果一个操作需要独立撤销、重做或记录,就应该将其作为单独命令;否则可以考虑合并到更大的命令中。