命令模式通过将请求封装为对象,实现调用者与接收者解耦,支持撤销、重做、命令队列等功能。其核心角色包括命令接口、具体命令、接收者、调用者和客户端。以智能家居灯光控制为例,开灯、关灯操作被封装为命令对象,调用者(如遥控器)无需了解具体执行逻辑,仅通过调用命令的execute()和undo()方法即可完成操作与撤销。该模式适用于GUI应用、事务系统、宏录制等场景,提升系统扩展性与灵活性,但需注意避免过度设计、合理控制命令粒度及管理状态与内存。

在C++中,命令模式(Command Pattern)提供了一种非常实用的机制,它将一个请求封装成一个对象,从而允许我们用不同的请求、队列或者日志来参数化客户端。更重要的是,它为实现复杂系统的撤销(Undo)和重做(Redo)功能提供了坚实的基础,将请求的发送者与接收者彻底解耦,极大地提升了代码的灵活性和可维护性。在我看来,它不仅是一种设计模式,更是一种思维方式的转变,让我们能够以更抽象、更解耦的视角来处理系统中的各种操作。
命令模式的核心思想是将“操作”抽象化为一个独立的类。这意味着每一个具体的操作,比如“打开文件”、“保存文档”或者“调整亮度”,都不再是直接调用某个对象的方法,而是被封装成一个实现了统一接口的命令对象。这个命令对象包含了执行该操作所需的所有信息,包括接收者(执行操作的实际对象)以及操作的参数。
一个典型的命令模式实现会包含以下几个关键角色:
execute()
undo()
Command
Command
execute()
让我们通过一个简单的“智能家居灯光控制”的例子来理解:
立即学习“C++免费学习笔记(深入)”;
#include <iostream>
#include <vector>
#include <stack>
#include <memory> // For std::shared_ptr
// 1. Receiver(接收者)
class Light {
public:
void turnOn() {
std::cout << "Light is ON!" << std::endl;
}
void turnOff() {
std::cout << "Light is OFF!" << std::endl;
}
};
// 2. Command(命令接口)
class Command {
public:
virtual void execute() = 0;
virtual void undo() = 0; // 支持撤销操作
virtual ~Command() = default;
};
// 3. ConcreteCommand(具体命令)
class TurnOnLightCommand : public Command {
private:
Light& light; // 持有接收者引用
public:
TurnOnLightCommand(Light& l) : light(l) {}
void execute() override {
light.turnOn();
}
void undo() override {
light.turnOff(); // 开灯的撤销是关灯
}
};
class TurnOffLightCommand : public Command {
private:
Light& light;
public:
TurnOffLightCommand(Light& l) : light(l) {}
void execute() override {
light.turnOff();
}
void undo() override {
light.turnOn(); // 关灯的撤销是开灯
}
};
// 4. Invoker(调用者)
class RemoteControl {
private:
std::shared_ptr<Command> command;
std::stack<std::shared_ptr<Command>> history; // 用于撤销的命令历史栈
public:
void setCommand(std::shared_ptr<Command> cmd) {
command = cmd;
}
void pressButton() {
if (command) {
command->execute();
history.push(command); // 将执行过的命令压入历史栈
}
}
void pressUndo() {
if (!history.empty()) {
std::shared_ptr<Command> lastCommand = history.top();
history.pop();
lastCommand->undo(); // 执行上一个命令的撤销操作
} else {
std::cout << "No commands to undo." << std::endl;
}
}
};
// 5. Client(客户端)
int main() {
Light myLight; // 接收者
RemoteControl remote; // 调用者
// 创建具体命令对象
std::shared_ptr<Command> turnOn = std::make_shared<TurnOnLightCommand>(myLight);
std::shared_ptr<Command> turnOff = std::make_shared<TurnOffLightCommand>(myLight);
// 设置并执行命令
remote.setCommand(turnOn);
remote.pressButton(); // Light is ON!
remote.setCommand(turnOff);
remote.pressButton(); // Light is OFF!
// 执行撤销
remote.pressUndo(); // Light is ON! (撤销了关灯操作)
remote.pressUndo(); // Light is OFF! (撤销了开灯操作)
remote.pressUndo(); // No commands to undo.
return 0;
}在这个例子中,
RemoteControl
Light
turnOn()
turnOff()
Command
execute()
undo()
我经常听到有人问:“为什么不直接调用对象的方法呢?那样不是更简单吗?”这确实是个好问题。对于一些非常简单的、一次性的操作,直接调用当然没问题。但当系统开始变得复杂,特别是当你需要实现以下功能时,命令模式的价值就会凸显出来:
命令模式的引入,首先解决了调用者与接收者之间的紧密耦合。想象一下,如果你的
RemoteControl
Light
turnOn()
RemoteControl
if-else
通过命令模式,
RemoteControl
Command
TurnOnLightCommand
TurnOffFanCommand
RemoteControl
其次,命令模式使得请求可以被当作对象来处理。这意味着你可以将命令存储起来,比如放到一个队列中,实现请求的异步执行;或者记录下来,用于日志记录、事务处理,甚至宏录制。我个人觉得,这种将“行为”对象化的能力,是其实现撤销/重做机制的关键。如果没有将操作封装成独立的对象,你很难追踪并逆转一系列复杂的操作序列。在GUI应用程序中,比如一个文本编辑器,用户执行的每一个“剪切”、“粘贴”、“输入字符”都可以是一个命令,而这些命令的序列构成了编辑器的历史,从而可以轻松地实现撤销和重做。
实现撤销(Undo)和重做(Redo)功能,是命令模式最引人注目的应用之一。其核心在于维护一个命令的历史记录。通常,我们会使用一个或两个栈来管理这些历史命令。
最常见的实现方式是使用一个栈来存储所有已经执行过的命令。每当一个命令被成功执行后,它就被压入这个“已执行命令栈”(
history
undo()
让我们再看看
RemoteControl
pressUndo()
class RemoteControl {
private:
// ... 其他成员 ...
std::stack<std::shared_ptr<Command>> history; // 用于撤销的命令历史栈
public:
// ... setCommand 和 pressButton ...
void pressUndo() {
if (!history.empty()) {
std::shared_ptr<Command> lastCommand = history.top();
history.pop();
lastCommand->undo(); // 执行上一个命令的撤销操作
// 如果需要支持Redo,这里需要将lastCommand压入一个Redo栈
} else {
std::cout << "No commands to undo." << std::endl;
}
}
};为了支持重做(Redo),我们需要引入第二个栈,通常称为“已撤销命令栈”(或者
redo
history
undo()
redo
redo
execute()
history
实现撤销操作时,有几个细节需要注意:
undo()
execute()
ConcreteCommand
execute()
execute()
undo()
Command
undo()
我个人在实现复杂撤销功能时,会倾向于让每个命令尽可能地“自包含”,即它知道如何执行自己,也知道如何撤销自己,而不需要外部过多干预。这有助于保持代码的清晰和模块化。
命令模式的应用远不止于简单的灯光控制或文本编辑器的撤销功能。在许多复杂的软件系统中,它都能发挥关键作用:
CopyCommand
Ctrl+C
CopyCommand
execute()
undo()
在实际项目中采用命令模式时,也需要进行一些考量:
总的来说,命令模式是一个功能强大且用途广泛的设计模式。它通过将请求对象化,为我们提供了极大的灵活性和可扩展性,尤其是在需要实现撤销/重做、日志记录、事务处理等复杂行为时,它几乎是一个不可或缺的工具。但和所有设计模式一样,它的应用也需要根据具体场景和需求进行权衡,避免过度工程。
以上就是C++命令模式实现请求封装与撤销操作的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号