1. 为什么需要命令模式?
在软件开发中,我们经常会遇到这样的场景:需要将"请求"封装成对象,以便能够参数化其他对象,支持可撤销的操作,或者将操作记录到日志中。这就是命令模式要解决的问题。
想象一下餐厅点餐的场景。顾客(Client)向服务员(Invoker)下单,服务员并不关心具体的烹饪过程,而是将订单(Command)交给厨师(Receiver)处理。这个过程中,订单就是一个命令对象,它封装了具体的烹饪指令。
在C++中实现命令模式,主要有以下几个核心组件:
- Command(命令接口):声明执行操作的接口
- ConcreteCommand(具体命令):将一个接收者对象绑定于一个动作,实现执行操作
- Client(客户端):创建具体命令对象并设置其接收者
- Invoker(调用者):要求命令执行请求
- Receiver(接收者):知道如何实施与执行一个请求相关的操作
2. 命令模式的C++实现详解
2.1 基础接口定义
首先定义命令接口,这是所有具体命令的基类:
cpp复制class Command {
public:
virtual ~Command() = default;
virtual void execute() = 0;
virtual void undo() = 0; // 支持撤销操作
};
2.2 具体命令实现
假设我们有一个灯光控制场景,实现开灯命令:
cpp复制class Light {
public:
void on() { std::cout << "Light is on" << std::endl; }
void off() { std::cout << "Light is off" << std::endl; }
};
class LightOnCommand : public Command {
Light& light;
public:
explicit LightOnCommand(Light& l) : light(l) {}
void execute() override { light.on(); }
void undo() override { light.off(); }
};
2.3 调用者实现
遥控器作为调用者,可以设置并执行命令:
cpp复制class RemoteControl {
std::unique_ptr<Command> slot;
public:
void setCommand(std::unique_ptr<Command> cmd) {
slot = std::move(cmd);
}
void buttonPressed() {
if (slot) slot->execute();
}
};
2.4 客户端使用示例
cpp复制int main() {
Light livingRoomLight;
auto lightOn = std::make_unique<LightOnCommand>(livingRoomLight);
RemoteControl remote;
remote.setCommand(std::move(lightOn));
remote.buttonPressed(); // 输出: Light is on
return 0;
}
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 实现命令队列
命令模式非常适合实现命令队列和日志系统:
cpp复制class CommandQueue {
std::queue<std::unique_ptr<Command>> queue;
public:
void addCommand(std::unique_ptr<Command> cmd) {
queue.push(std::move(cmd));
}
void processCommands() {
while (!queue.empty()) {
auto cmd = std::move(queue.front());
queue.pop();
cmd->execute();
// 可以在这里记录日志或实现重做功能
