命令模式在复杂系统中的优势体现在解耦、可扩展性、事务处理支持、宏命令实现等方面。首先,它通过将请求封装为对象,使调用者与接收者解耦;其次,新增功能只需扩展新命令类,符合开闭原则;第三,命令对象可被记录、序列化,便于事务回滚与日志追踪;第四,支持宏命令组合,实现多操作一体化执行。_undo/redo的实现依赖于命令对象保存执行前状态或使用备忘录模式,并通过两个栈管理历史记录。命令模式常与备忘录模式协作提升撤销能力,与组合模式构建宏命令,与工厂模式解耦命令创建,与策略模式协同实现算法选择,从而增强系统健壮性与灵活性。

命令模式,说白了,就是把一个请求封装成一个对象。这样做的核心价值在于,它把请求的发送者和接收者彻底解耦了。你不再需要知道具体是哪个对象来执行这个操作,也不需要知道执行的细节,你只需要告诉一个“命令”对象去执行就行了。更妙的是,因为请求被封装了,它天然就支持了参数化、队列化,甚至能轻易实现撤销(Undo)和重做(Redo)操作,这在很多需要历史记录或事务处理的系统里简直是神来之笔。

实现C++的命令模式,通常需要定义一个抽象的命令接口,具体的命令类实现这个接口,然后是命令的接收者(执行实际操作的对象),以及命令的调用者(触发命令的对象)。对于撤销操作,关键在于命令对象需要在执行前保存状态,或者提供一个逆向操作。
#include <iostream>
#include <vector>
#include <string>
#include <memory> // For std::shared_ptr
#include <stack> // For undo/redo history
// 1. 抽象命令接口 (ICommand)
class ICommand {
public:
virtual ~ICommand() = default;
virtual void execute() = 0;
virtual void undo() = 0; // 支持撤销
};
// 2. 接收者 (Receiver) - 实际执行操作的对象
class Light {
private:
std::string location;
bool isOn = false;
public:
Light(const std::string& loc) : location(loc) {}
void on() {
if (!isOn) {
std::cout << location << "灯亮了。" << std::endl;
isOn = true;
}
}
void off() {
if (isOn) {
std::cout << location << "灯灭了。" << std::endl;
isOn = false;
}
}
bool getIsOn() const { return isOn; }
};
// 3. 具体命令 (Concrete Commands)
class LightOnCommand : public ICommand {
private:
Light& light;
bool previousState; // 记录执行前的状态,用于撤销
public:
LightOnCommand(Light& l) : light(l), previousState(l.getIsOn()) {}
void execute() override {
previousState = light.getIsOn(); // 确保记录的是当前状态
light.on();
}
void undo() override {
if (previousState) { // 如果之前是亮的,撤销就是让它亮
light.on();
} else { // 如果之前是灭的,撤销就是让它灭
light.off();
}
std::cout << "撤销:将" << (previousState ? "亮" : "灭") << "状态恢复。" << std::endl;
}
};
class LightOffCommand : public ICommand {
private:
Light& light;
bool previousState; // 记录执行前的状态,用于撤销
public:
LightOffCommand(Light& l) : light(l), previousState(l.getIsOn()) {}
void execute() override {
previousState = light.getIsOn(); // 确保记录的是当前状态
light.off();
}
void undo() override {
if (previousState) { // 如果之前是亮的,撤销就是让它亮
light.on();
} else { // 如果之前是灭的,撤销就是让它灭
light.off();
}
std::cout << "撤销:将" << (previousState ? "亮" : "灭") << "状态恢复。" << std::endl;
}
};
// 4. 调用者 (Invoker) - 持有命令并触发执行
class RemoteControl {
private:
std::stack<std::shared_ptr<ICommand>> history; // 已执行的命令历史
std::stack<std::shared_ptr<ICommand>> redoHistory; // 撤销后的命令历史
public:
void setCommand(std::shared_ptr<ICommand> command) {
// 在这里,我们通常会直接执行,或者添加到队列中
// 为了演示,我们直接执行并添加到历史
command->execute();
history.push(command);
// 执行新命令后,清空redo历史,因为新的操作会覆盖之前的redo点
while (!redoHistory.empty()) {
redoHistory.pop();
}
}
void undoLastCommand() {
if (!history.empty()) {
std::shared_ptr<ICommand> lastCommand = history.top();
history.pop();
lastCommand->undo();
redoHistory.push(lastCommand); // 将撤销的命令放入redo历史
} else {
std::cout << "没有更多可撤销的操作了。" << std::endl;
}
}
void redoLastUndo() {
if (!redoHistory.empty()) {
std::shared_ptr<ICommand> lastRedoCommand = redoHistory.top();
redoHistory.pop();
lastRedoCommand->execute(); // 重做就是再次执行
history.push(lastRedoCommand); // 将重做的命令放回历史
} else {
std::cout << "没有更多可重做的操作了。" << std::endl;
}
}
};
// 客户端代码 (Client)
// int main() {
// Light livingRoomLight("客厅");
// Light kitchenLight("厨房");
// std::shared_ptr<ICommand> livingRoomLightOn = std::make_shared<LightOnCommand>(livingRoomLight);
// std::shared_ptr<ICommand> livingRoomLightOff = std::make_shared<LightOffCommand>(livingRoomLight);
// std::shared_ptr<ICommand> kitchenLightOn = std::make_shared<LightOnCommand>(kitchenLight);
// std::shared_ptr<ICommand> kitchenLightOff = std::make_shared<LightOffCommand>(kitchenLight);
// RemoteControl remote;
// // 执行操作
// remote.setCommand(livingRoomLightOn);
// remote.setCommand(kitchenLightOn);
// remote.setCommand(livingRoomLightOff);
// std::cout << "\n--- 尝试撤销 ---\n";
// remote.undoLastCommand(); // 撤销客厅灯灭 -> 客厅灯亮
// remote.undoLastCommand(); // 撤销厨房灯亮 -> 厨房灯灭
// remote.undoLastCommand(); // 撤销客厅灯亮 -> 客厅灯灭
// remote.undoLastCommand(); // 没有更多可撤销的了
// std::cout << "\n--- 尝试重做 ---\n";
// remote.redoLastUndo(); // 重做客厅灯亮
// remote.redoLastUndo(); // 重做厨房灯亮
// remote.redoLastUndo(); // 重做客厅灯灭
// remote.redoLastUndo(); // 没有更多可重做的了
// std::cout << "\n--- 新操作会清除重做历史 ---\n";
// remote.setCommand(kitchenLightOff); // 厨房灯灭
// remote.redoLastUndo(); // 此时重做历史已被清空
// return 0;
// }在我看来,命令模式在复杂系统中的优势简直是多方面的,它不仅仅是解耦那么简单。首先,最直接的好处就是解耦:调用者(比如一个UI按钮)不再需要知道它背后的具体操作是谁来完成的,也不需要知道怎么完成。它只需要持有一个
ICommand
execute()
立即学习“C++免费学习笔记(深入)”;

其次,它带来了极高的可扩展性。想象一下,如果你的系统需要增加一个新的功能,比如“打开窗帘”或者“调节空调温度”,你只需要创建新的
Command
Receiver
Invoker
再者,命令模式天生就是为事务处理和日志记录而生的。因为每个操作都被封装成了一个对象,你可以很方便地把这些命令对象序列化、存储起来,形成操作日志,或者在系统崩溃后进行恢复。这对于需要审计追踪、或者确保数据一致性的场景特别有用。比如,在一个数据库事务中,每一步操作都可以是一个命令,如果其中一步失败,你可以很方便地回滚所有已执行的命令。

还有一点,我觉得特别有意思的是,它可以很自然地实现宏命令(Macro Command)。就是把一系列命令组合成一个更大的命令。比如,一个“晚安模式”命令,它可能包含“关灯”、“拉窗帘”、“设置闹钟”等多个子命令。这在用户操作流程复杂,或者需要批量处理的场景下,简直是太方便了。你可以把用户的一系列操作录制下来,然后作为一个整体回放,或者保存成一个脚本。这种能力,如果不用命令模式,你可能得写一堆复杂的条件判断和函数调用,那代码简直没法看。
优雅地处理命令模式中的撤销和重做,这其实是命令模式的一个亮点,但实现起来也有些讲究。核心思想是,每个命令在执行时,都要有能力记录下足够的信息,以便在撤销时能恢复到执行前的状态。
最常见的做法,就像我在代码示例里展示的,是在每个具体命令类内部,存储它所操作的接收者在执行
execute()
LightOnCommand
on()
undo()
然而,当接收者的状态非常复杂时,直接在命令里存储所有状态可能会导致命令对象变得非常臃肿,甚至出现循环依赖。这时候,我们通常会引入备忘录模式(Memento Pattern)来协作。命令对象不再直接存储状态,而是让接收者生成一个“备忘录”对象(memento),这个备忘录包含了接收者在某个时刻的所有内部状态。命令对象只存储这个备忘录,在撤销时,再把备忘录还给接收者,让接收者恢复到之前的状态。这种方式把状态存储的职责从命令中解耦出去,让设计更清晰。
对于撤销和重做的管理,通常会使用两个栈:一个
history
undoStack
redoHistory
redoStack
history
redoHistory
history
undo()
redoHistory
redoHistory
execute()
history
这里面有个小挑战,就是并非所有命令都能被撤销。有些操作是破坏性的,或者不可逆的,比如“删除文件”。对于这类命令,你可能需要在设计时就明确它们不支持撤销,或者提供一个警告。另外,性能也是一个考量点。如果每次操作都存储大量的状态,那么撤销栈可能会占用大量内存。这时,可能需要考虑增量式状态存储,只记录状态的变化,而不是整个状态的快照。或者,对于非常长的操作序列,可以考虑限制撤销历史的深度。
命令模式本身已经很强大了,但它并不是孤立存在的。在实际的复杂系统中,它经常会与其他设计模式“手拉手”协作,共同构建出更健壮、更灵活的架构。
首先,我前面提到了备忘录模式(Memento Pattern)。这俩简直是天作之合,尤其是在处理撤销/重做功能时。当命令需要保存接收者的复杂状态以便撤销时,如果命令自己去管理这些状态,会变得非常臃肿且耦合。备忘录模式让接收者负责创建和恢复自己的状态(通过一个备忘录对象),命令对象只需要持有这个轻量级的备忘录,在需要时将其传回接收者。这让命令专注于“做什么”和“如何撤销”,而状态的“如何保存和恢复”则由接收者和备忘录模式来处理,职责分离得非常清晰。
接着是组合模式(Composite Pattern)。这个模式允许你将对象组合成树形结构以表示“部分-整体”的层次结构。在命令模式中,这意味着你可以创建一个
MacroCommand
ICommand
ICommand
MacroCommand
然后是工厂模式(Factory Method / Abstract Factory)。在很多情况下,你可能需要根据用户的输入、配置或者某些运行时条件来动态地创建命令对象。如果直接在客户端代码中
new
CommandFactory
ICommand
最后,我想提一下它和策略模式(Strategy Pattern)的区别与联系。有时候这俩容易混淆。策略模式关注的是“如何做一件事”,它封装的是算法或行为的不同实现,这些实现可以互换。而命令模式关注的是“做什么”,它封装的是一个请求本身。一个命令对象通常包含了一个动作的接收者和执行这个动作所需的所有参数。虽然它们都使用了多态性来封装行为,但它们的意图和应用场景有所不同。不过,在某些复杂的场景下,一个命令的
execute()
以上就是怎样实现C++的命令模式 请求封装与撤销操作支持的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号