第一章:缘起------一个程序员的"后悔药"梦想
🎭 场景设定:深夜两点,程序员小明正在疯狂coding,一不小心按下了Ctrl+A然后Delete... "啊!我的代码!" 这一刻,他多么希望有个"后悔药"啊!
1.1 那些年,我们追悔莫及的操作
还记得第一次使用Word时,不小心删除了整段文字的那种绝望吗?或者在玩《我的世界》时,手滑拆掉了辛苦建造的房子?这些痛苦的经历催生了一个伟大的需求:我们需要撤销功能!
但实现撤销功能可不是简单的事。想象一下,如果每个操作都要保存整个文档的状态,你的内存很快就会像春运的火车站一样拥挤。这时候,命令模式就像一位智慧的魔法师,挥舞着魔杖说:"别担心,我有办法!"
1.2 命令模式的"前世今生"
命令模式最早出现在1994年,由四位软件界的"摇滚明星"------Gamma、Helm、Johnson和Vlissides(合称GoF)在他们的经典著作中提出。这就像音乐界的披头士,设计模式界的"Fab Four"!
有趣的时间线:
- 1994年:命令模式正式"出道"
- 2000年:成为GUI框架的"标配明星"
- 2010年:在游戏开发中大放异彩
- 2020年:在微服务架构中焕发第二春
第二章:命令模式的"魔法学校"
2.1 五个关键角色:命令模式的"复仇者联盟"
想象一下,命令模式就像一场精彩的戏剧,有五个关键角色:
客户 +下订单() 服务员 -订单: 命令 +记下订单() +喊单() <<抽象>> 订单接口 +做菜() +退菜() 具体订单 -厨师: 厨房 -菜品详情 +做菜() +退菜() 厨房 +炒菜() +回收食材()
角色介绍(戏剧版):
角色 | 剧中身份 | 现实对应 | 个性特点 |
---|---|---|---|
命令(Command) | 订单模板 | 接口/抽象类 | 严格的"领导",只定规矩不干活 |
具体命令(ConcreteCommand) | 具体订单 | 实现类 | 勤劳的"传令兵",知道找谁干活 |
客户端(Client) | 顾客 | 应用程序 | "金主爸爸",提需求的人 |
调用者(Invoker) | 服务员 | 按钮/菜单 | 灵活的"协调员",不干活但指挥别人 |
接收者(Receiver) | 厨师 | 业务对象 | 真正的"实干家",默默完成所有工作 |
2.2 魔法咒语:命令模式的三大核心原则
原则一:封装请求
"不要直接告诉厨师怎么做菜,写张订单给他!"
原则二:解耦发送者和接收者
"顾客不需要知道厨师的手机号,通过服务员传话就行!"
原则三:支持高级操作
"可以取消订单、重复下单,甚至来个套餐组合!"
第三章:实战演练------文本编辑器的"时光机"
3.1 需求分析:我们要建造什么样的"时光机"?
想象一下,我们要开发一个超级文本编辑器,具备以下超能力:
- ✅ 可以撤销任何操作(就像Ctrl+Z)
- ✅ 可以重做被撤销的操作(就像Ctrl+Y)
- ✅ 支持操作历史记录
- ✅ 甚至可以实现"宏命令"(一键完成多个操作)
3.2 代码实现:一步步建造我们的"时光机"
第一步:创建"文档"类(接收者)
cpp
/**
* @brief 文档类 - 文本编辑器的"画布"
*
* 这个类就像画家的画布,负责实际的内容操作。
* 想象一下,这就是你的Word文档本体!
*/
class 魔法文档 {
private:
std::string 内容_; // 文档内容
size_t 光标位置_; // 当前光标位置
public:
魔法文档() : 内容_(""), 光标位置_(0) {
std::cout << "🎨 创建了一个全新的魔法画布!" << std::endl;
}
/**
* @brief 插入文本 - 就像用画笔在画布上作画
*
* @param 文字 要插入的魔法文字
*/
void 插入文字(const std::string& 文字) {
内容_.insert(光标位置_, 文字);
光标位置_ += 文字.length();
std::cout << "✏️ 插入了文字: \"" << 文字
<< "\",现在内容是: \"" << 内容_ << "\"" << std::endl;
}
/**
* @brief 删除文本 - 就像用橡皮擦擦除画作
*
* @param 长度 要擦除的字符数
* @return 被擦除的文字(留着以后可能要用)
*/
std::string 删除文字(size_t 长度) {
// 确保不会擦除过头
if (光标位置_ < 长度) {
长度 = 光标位置_;
}
size_t 起始位置 = 光标位置_ - 长度;
std::string 被删除的文字 = 内容_.substr(起始位置, 长度);
内容_.erase(起始位置, 长度);
光标位置_ = 起始位置;
std::cout << "🧽 擦除了文字: \"" << 被删除的文字
<< "\",现在内容是: \"" << 内容_ << "\"" << std::endl;
return 被删除的文字;
}
// 其他魔法方法...
void 移动光标(size_t 位置) {
光标位置_ = 位置;
std::cout << "🔍 光标移动到了位置: " << 位置 << std::endl;
}
std::string 获取内容() const { return 内容_; }
};
第二步:创建"命令"接口和具体命令
cpp
/**
* @brief 命令接口 - 所有魔法指令的"祖宗"
*
* 这就像魔法世界的基本咒语格式,所有具体咒语都要遵循这个格式。
*/
class 魔法命令 {
public:
virtual ~魔法命令() = default;
/**
* @brief 执行咒语 - 让魔法生效!
*/
virtual void 执行() = 0;
/**
* @brief 撤销咒语 - 时光倒流!
*/
virtual void 撤销() = 0;
/**
* @brief 获取咒语描述 - 告诉别人这是什么魔法
*/
virtual std::string 获取描述() const = 0;
};
/**
* @brief 插入文字命令 - 具体的"书写咒语"
*
* 这个咒语专门负责在文档上写入文字。
*/
class 书写咒语 : public 魔法命令 {
private:
魔法文档& 文档_; // 要对哪个文档施法
std::string 文字_; // 要写入什么文字
size_t 位置_; // 在什么位置写入
public:
/**
* @brief 构造函数 - 准备咒语材料
*/
书写咒语(魔法文档& 文档, const std::string& 文字)
: 文档_(文档), 文字_(文字), 位置_(文档.获取光标位置()) {
std::cout << "📖 准备书写咒语: \"" << 文字 << "\"" << std::endl;
}
void 执行() override {
文档_.移动光标(位置_);
文档_.插入文字(文字_);
std::cout << "✨ 书写咒语生效了!" << std::endl;
}
void 撤销() override {
文档_.移动光标(位置_);
文档_.删除文字(文字_.length());
文档_.移动光标(位置_); // 把光标移回原处
std::cout << "⏪ 书写咒语被撤销了!" << std::endl;
}
std::string 获取描述() const override {
return "书写咒语: \"" + 文字_ + "\"";
}
};
/**
* @brief 删除文字命令 - 具体的"清除咒语"
*
* 这个咒语专门负责清除文档上的文字。
*/
class 清除咒语 : public 魔法命令 {
private:
魔法文档& 文档_;
size_t 长度_;
std::string 被删除的文字_; // 记住删除了什么,以便撤销
size_t 位置_;
public:
清除咒语(魔法文档& 文档, size_t 长度)
: 文档_(文档), 长度_(长度), 位置_(文档.获取光标位置()) {
std::cout << "🧹 准备清除咒语,长度: " << 长度 << std::endl;
}
void 执行() override {
文档_.移动光标(位置_);
被删除的文字_ = 文档_.删除文字(长度_);
std::cout << "✨ 清除咒语生效了!" << std::endl;
}
void 撤销() override {
文档_.移动光标(位置_);
文档_.插入文字(被删除的文字_);
std::cout << "⏪ 清除咒语被撤销了!" << std::endl;
}
std::string 获取描述() const override {
return "清除咒语: \"" + 被删除的文字_ + "\"";
}
};
第三步:创建"命令历史"管理器(调用者)
cpp
/**
* @brief 命令历史 - 魔法世界的"时光机器"
*
* 这个类就像一台时光机,记录所有操作,支持前进和后退。
*/
class 时光机器 {
private:
std::stack<std::unique_ptr<魔法命令>> 撤销栈_; // 已经执行的咒语
std::stack<std::unique_ptr<魔法命令>> 重做栈_; // 已经撤销的咒语
public:
/**
* @brief 执行新咒语 - 让时光向前流动
*
* @param 咒语 要执行的新咒语
*/
void 执行咒语(std::unique_ptr<魔法命令> 咒语) {
咒语->执行();
撤销栈_.push(std::move(咒语));
// 执行新咒语时,清空重做栈(就像改变了时间线)
while (!重做栈_.empty()) {
重做栈_.pop();
}
std::cout << "⏩ 新咒语已加入时间线!" << std::endl;
this->显示时间线状态();
}
/**
* @brief 撤销咒语 - 让时光倒流一步
*
* @return 成功撤销返回true,否则false
*/
bool 撤销() {
if (撤销栈_.empty()) {
std::cout << "🚫 无法撤销,已经是最早的时间点了!" << std::endl;
return false;
}
auto 咒语 = std::move(撤销栈_.top());
撤销栈_.pop();
咒语->撤销();
重做栈_.push(std::move(咒语));
std::cout << "⏪ 时光倒流一步!" << std::endl;
this->显示时间线状态();
return true;
}
/**
* @brief 重做咒语 - 让时光重新前进
*
* @return 成功重做返回true,否则false
*/
bool 重做() {
if (重做栈_.empty()) {
std::cout << "🚫 无法重做,已经是最新的时间点了!" << std::endl;
return false;
}
auto 咒语 = std::move(重做栈_.top());
重做栈_.pop();
咒语->执行();
撤销栈_.push(std::move(咒语));
std::cout << "⏩ 时光重新前进!" << std::endl;
this->显示时间线状态();
return true;
}
/**
* @brief 显示当前时间线状态
*/
void 显示时间线状态() const {
std::cout << "📊 时间线状态 === "
<< "撤销栈大小: " << 撤销栈_.size()
<< ", 重做栈大小: " << 重做栈_.size()
<< std::endl;
}
};
第四步:客户端代码 - 开始我们的魔法表演!
cpp
/**
* @brief 主函数 - 魔法表演的舞台
*
* 这里我们将演示命令模式的完整魔法效果!
*/
int main() {
std::cout << "🎪 欢迎来到命令模式魔法秀!" << std::endl;
std::cout << "=========================================" << std::endl;
// 创建我们的魔法画布
魔法文档 我的画布;
时光机器 我的时光机;
std::cout << "\n🌟 第一幕:开始创作之旅" << std::endl;
std::cout << "-----------------------------------------" << std::endl;
// 执行一系列创作咒语
我的时光机.执行咒语(std::make_unique<书写咒语>(我的画布, "你好"));
我的时光机.执行咒语(std::make_unique<书写咒语>(我的画布, ",魔法世界"));
我的时光机.执行咒语(std::make_unique<书写咒语>(我的画布, "!"));
std::cout << "\n📝 当前画布内容: \"" << 我的画布.获取内容() << "\"" << std::endl;
std::cout << "\n🔄 第二幕:体验时光倒流" << std::endl;
std::cout << "-----------------------------------------" << std::endl;
// 测试撤销功能(就像按Ctrl+Z)
我的时光机.撤销();
std::cout << "撤销后内容: \"" << 我的画布.获取内容() << "\"" << std::endl;
我的时光机.撤销();
std::cout << "再次撤销后内容: \"" << 我的画布.获取内容() << "\"" << std::endl;
std::cout << "\n⚡ 第三幕:体验时光前进" << std::endl;
std::cout << "-----------------------------------------" << std::endl;
// 测试重做功能(就像按Ctrl+Y)
我的时光机.重做();
std::cout << "重做后内容: \"" << 我的画布.获取内容() << "\"" << std::endl;
std::cout << "\n🎭 第四幕:混合魔法表演" << std::endl;
std::cout << "-----------------------------------------" << std::endl;
// 来点复杂的操作组合
我的时光机.执行咒语(std::make_unique<清除咒语>(我的画布, 2));
std::cout << "清除后内容: \"" << 我的画布.获取内容() << "\"" << std::endl;
// 再次撤销看看
我的时光机.撤销();
std::cout << "最终内容: \"" << 我的画布.获取内容() << "\"" << std::endl;
std::cout << "\n🎉 魔法表演圆满结束!" << std::endl;
std::cout << "=========================================" << std::endl;
return 0;
}
3.3 运行结果:观看我们的魔法秀
编译运行上面的代码,你会看到一场精彩的"魔法表演":
bash
# 编译命令
g++ -std=c++17 -o 魔法编辑器 魔法编辑器.cpp
# 运行结果
🎪 欢迎来到命令模式魔法秀!
=========================================
🌟 第一幕:开始创作之旅
-----------------------------------------
🎨 创建了一个全新的魔法画布!
📖 准备书写咒语: "你好"
🔍 光标移动到了位置: 0
✏️ 插入了文字: "你好",现在内容是: "你好"
✨ 书写咒语生效了!
⏩ 新咒语已加入时间线!
📊 时间线状态 === 撤销栈大小: 1, 重做栈大小: 0
# ... 更多精彩的输出 ...
第四章:进阶魔法------智能家居的"情景模式"
4.1 现实需求:懒人必备的智能家居
想象一下这样的场景:
- 🏠 回家模式:开门瞬间,灯光亮起、空调启动、音乐播放
- 🎬 影院模式:一键关闭灯光、拉上窗帘、打开投影仪
- 🛌 睡眠模式:逐渐调暗灯光、关闭电器、设置空调温度
这些"情景模式"就是命令模式的完美应用场景!
4.2 代码实现:打造智能家居控制系统
智能设备类(接收者们)
cpp
/**
* @brief 智能灯光 - 会变色的魔法灯泡
*/
class 智能灯光 {
private:
bool 开关状态_ = false;
int 亮度_ = 100;
std::string 颜色_ = "白色";
public:
void 开启() {
开关状态_ = true;
std::cout << "💡 灯光已开启,亮度: " << 亮度_ << "%,颜色: " << 颜色_ << std::endl;
}
void 关闭() {
开关状态_ = false;
std::cout << "🌙 灯光已关闭" << std::endl;
}
void 设置亮度(int 亮度) {
亮度_ = 亮度;
if (开关状态_) {
std::cout << "🔅 灯光亮度调整为: " << 亮度_ << "%" << std::endl;
}
}
void 设置颜色(const std::string& 颜色) {
颜色_ = 颜色;
if (开关状态_) {
std::cout << "🎨 灯光颜色变为: " << 颜色_ << std::endl;
}
}
};
/**
* @brief 智能空调 - 懂你的温度管家
*/
class 智能空调 {
private:
bool 开关状态_ = false;
int 温度_ = 26;
std::string 模式_ = "自动";
public:
void 开启() {
开关状态_ = true;
std::cout << "❄️ 空调已开启,温度: " << 温度_ << "℃,模式: " << 模式_ << std::endl;
}
void 关闭() {
开关状态_ = false;
std::cout << "☀️ 空调已关闭" << std::endl;
}
void 设置温度(int 温度) {
温度_ = 温度;
if (开关状态_) {
std::cout << "🌡️ 空调温度设置为: " << 温度_ << "℃" << std::endl;
}
}
void 设置模式(const std::string& 模式) {
模式_ = 模式;
if (开关状态_) {
std::cout << "🌀 空调模式改为: " << 模式_ << std::endl;
}
}
};
/**
* @brief 智能窗帘 - 自动开合的魔法窗帘
*/
class 智能窗帘 {
private:
bool 开关状态_ = false; // true表示打开,false表示关闭
int 开合程度_ = 0; // 0-100表示开合百分比
public:
void 打开() {
开关状态_ = true;
开合程度_ = 100;
std::cout << "🪟 窗帘已完全打开" << std::endl;
}
void 关闭() {
开关状态_ = false;
开合程度_ = 0;
std::cout << "🚪 窗帘已完全关闭" << std::endl;
}
void 设置开合(int 程度) {
开合程度_ = 程度;
开关状态_ = (程度 > 0);
std::cout << "📏 窗帘开合程度设置为: " << 开合程度_ << "%" << std::endl;
}
};
情景模式命令(宏命令)
cpp
/**
* @brief 情景模式命令 - 一键执行多个操作的"组合咒语"
*
* 这就像魔法世界的高级复合咒语,一个咒语包含多个效果。
*/
class 回家模式命令 : public 魔法命令 {
private:
智能灯光& 灯光_;
智能空调& 空调_;
智能窗帘& 窗帘_;
public:
回家模式命令(智能灯光& 灯, 智能空调& 空, 智能窗帘& 窗)
: 灯光_(灯), 空调_(空), 窗帘_(窗) {}
void 执行() override {
std::cout << "\n🏠 启动回家模式!" << std::endl;
std::cout << "=======================" << std::endl;
灯光_.开启();
灯光_.设置亮度(80);
灯光_.设置颜色("暖黄色");
空调_.开启();
空调_.设置温度(24);
空调_.设置模式("舒适");
窗帘_.打开();
std::cout << "=======================" << std::endl;
std::cout << "🎉 回家模式设置完成!" << std::endl;
}
void 撤销() override {
std::cout << "\n🔄 撤销回家模式..." << std::endl;
灯光_.关闭();
空调_.关闭();
窗帘_.关闭();
}
std::string 获取描述() const override {
return "回家模式(灯光+空调+窗帘)";
}
};
class 影院模式命令 : public 魔法命令 {
private:
智能灯光& 灯光_;
智能窗帘& 窗帘_;
public:
影院模式命令(智能灯光& 灯, 智能窗帘& 窗)
: 灯光_(灯), 窗帘_(窗) {}
void 执行() override {
std::cout << "\n🎬 启动影院模式!" << std::endl;
std::cout << "=======================" << std::endl;
灯光_.设置亮度(10);
灯光_.设置颜色("蓝色");
窗帘_.关闭();
std::cout << "🍿 准备好爆米花,电影开始!" << std::endl;
std::cout << "=======================" << std::endl;
}
void 撤销() override {
std::cout << "\n🔄 撤销影院模式..." << std::endl;
灯光_.设置亮度(100);
灯光_.设置颜色("白色");
窗帘_.打开();
}
std::string 获取描述() const override {
return "影院模式(调暗灯光+关闭窗帘)";
}
};
智能遥控器(调用者)
cpp
/**
* @brief 智能遥控器 - 情景模式的指挥中心
*
* 就像魔法师的魔杖,一挥就能触发复杂的魔法效果。
*/
class 智能遥控器 {
private:
std::vector<std::unique_ptr<魔法命令>> 按钮_; // 每个按钮对应一个情景模式
public:
/**
* @brief 设置按钮功能
*
* @param 按钮编号 哪个按钮(从0开始)
* @param 命令 要执行的命令
*/
void 设置按钮(int 按钮编号, std::unique_ptr<魔法命令> 命令) {
if (按钮编号 >= 按钮_.size()) {
按钮_.resize(按钮编号 + 1);
}
按钮_[按钮编号] = std::move(命令);
std::cout << "🔘 按钮" << 按钮编号 << "设置完成!" << std::endl;
}
/**
* @brief 按下按钮 - 触发情景模式
*
* @param 按钮编号 按哪个按钮
*/
void 按下按钮(int 按钮编号) {
if (按钮编号 < 按钮_.size() && 按钮_[按钮编号]) {
std::cout << "\n👆 按下按钮" << 按钮编号 << "..." << std::endl;
按钮_[按钮编号]->执行();
} else {
std::cout << "❌ 按钮" << 按钮编号 << "未设置功能!" << std::endl;
}
}
/**
* @brief 显示所有按钮功能
*/
void 显示按钮功能() {
std::cout << "\n📋 遥控器按钮功能列表:" << std::endl;
for (size_t i = 0; i < 按钮_.size(); ++i) {
if (按钮_[i]) {
std::cout << " 按钮" << i << ": " << 按钮_[i]->获取描述() << std::endl;
}
}
}
};
客户端演示代码
cpp
/**
* @brief 智能家居演示 - 体验未来生活
*/
void 演示智能家居() {
std::cout << "\n🤖 智能家居系统启动中..." << std::endl;
std::cout << "=========================================" << std::endl;
// 创建智能设备
智能灯光 客厅灯;
智能空调 客厅空调;
智能窗帘 客厅窗帘;
// 创建智能遥控器
智能遥控器 我的遥控器;
时光机器 家居时光机;
// 设置情景模式
我的遥控器.设置按钮(0,
std::make_unique<回家模式命令>(客厅灯, 客厅空调, 客厅窗帘));
我的遥控器.设置按钮(1,
std::make_unique<影院模式命令>(客厅灯, 客厅窗帘));
// 显示功能列表
我的遥控器.显示按钮功能();
std::cout << "\n🏠 场景一:下班回家" << std::endl;
std::cout << "-----------------------------------------" << std::endl;
// 执行回家模式
家居时光机.执行咒语(std::make_unique<回家模式命令>(客厅灯, 客厅空调, 客厅窗帘));
std::cout << "\n⏸️ 休息一会儿..." << std::endl;
std::cout << "-----------------------------------------" << std::endl;
std::cout << "\n🎬 场景二:家庭影院时间" << std::endl;
std::cout << "-----------------------------------------" << std::endl;
// 执行影院模式
家居时光机.执行咒语(std::make_unique<影院模式命令>(客厅灯, 客厅窗帘));
std::cout << "\n🔄 场景三:突然想撤销影院模式" << std::endl;
std::cout << "-----------------------------------------" << std::endl;
// 撤销影院模式
家居时光机.撤销();
std::cout << "\n🔚 演示结束,关闭所有设备" << std::endl;
std::cout << "=========================================" << std::endl;
}
第五章:命令模式的"超能力"解析
5.1 八大优势:为什么命令模式这么"香"?
超能力 | 具体表现 | 现实比喻 |
---|---|---|
🔗 解耦魔法 | 发送者和接收者完全独立 | 顾客不需要认识厨师 |
⏪ 时光倒流 | 完美支持撤销/重做 | 写作文时的"撤销"按钮 |
📚 操作日志 | 可以记录所有操作历史 | 飞机的黑匣子 |
👥 任务队列 | 支持操作排队执行 | 银行取号排队系统 |
🎯 宏命令 | 组合多个操作为一个 | 电脑的一键清理 |
⚡ 异步执行 | 支持后台执行命令 | 外卖订单后台处理 |
🎭 事务处理 | 要么全成功要么全失败 | 银行转账事务 |
🔧 动态配置 | 运行时改变命令绑定 | 游戏手柄按键自定义 |
5.2 三大应用场景:命令模式的"主战场"
场景一:GUI应用程序
- 菜单项、工具栏按钮、快捷键
- 每个UI控件都绑定一个命令对象
- 支持统一的撤销/重做系统
场景二:游戏开发
- 玩家操作记录(用于回放功能)
- AI行为调度系统
- 游戏状态保存/加载
场景三:事务系统
- 数据库事务管理
- 微服务架构中的 Saga 模式
- 分布式系统中的操作日志
第六章:实战技巧与最佳实践
6.1 性能优化:让命令模式"飞起来"
技巧一:使用对象池
cpp
// 命令对象池 - 避免频繁创建销毁
class 命令对象池 {
private:
std::stack<std::unique_ptr<魔法命令>> 池子_;
public:
template<typename T, typename... Args>
std::unique_ptr<T> 获取命令(Args&&... args) {
if (池子_.empty()) {
return std::make_unique<T>(std::forward<Args>(args)...);
}
auto 命令 = std::move(池子_.top());
池子_.pop();
return std::unique_ptr<T>(static_cast<T*>(命令.release()));
}
void 归还命令(std::unique_ptr<魔法命令> 命令) {
池子_.push(std::move(命令));
}
};
技巧二:懒加载状态
cpp
// 只在需要时加载状态,节省内存
class 大文件操作命令 : public 魔法命令 {
private:
std::string 文件名_;
mutable std::unique_ptr<文件状态> 状态_; // 延迟加载
void 确保状态已加载() const {
if (!状态_) {
状态_ = std::make_unique<文件状态>(文件名_);
}
}
public:
void 撤销() override {
确保状态已加载(); // 只在撤销时加载状态
// ... 撤销操作
}
};
6.2 设计模式组合:命令模式的"好搭档"
组合一:命令模式 + 组合模式 = 宏命令系统
cpp
// 组合命令 - 可以包含其他命令的超级命令
class 组合命令 : public 魔法命令 {
private:
std::vector<std::unique_ptr<魔法命令>> 子命令列表_;
public:
void 添加命令(std::unique_ptr<魔法命令> 命令) {
子命令列表_.push_back(std::move(命令));
}
void 执行() override {
for (auto& 命令 : 子命令列表_) {
命令->执行();
}
}
void 撤销() override {
// 按相反顺序撤销
for (auto it = 子命令列表_.rbegin(); it != 子命令列表_.rend(); ++it) {
(*it)->撤销();
}
}
};
组合二:命令模式 + 备忘录模式 = 完美撤销系统
cpp
// 备忘录模式保存状态
class 文档备忘录 {
private:
std::string 内容_;
size_t 光标位置_;
public:
文档备忘录(const std::string& 内容, size_t 位置)
: 内容_(内容), 光标位置_(位置) {}
std::string 获取内容() const { return 内容_; }
size_t 获取光标位置() const { return 光标位置_; }
};
// 支持备忘录的命令
class 支持备忘录的命令 : public 魔法命令 {
protected:
virtual 文档备忘录 创建备忘录() const = 0;
virtual void 从备忘录恢复(const 文档备忘录&) = 0;
};
第七章:现代框架中的命令模式
7.1 Qt框架:QUndoCommand的强大威力
cpp
// Qt中的命令模式实现
class 我的撤销命令 : public QUndoCommand {
public:
我的撤销命令(文档类* 文档, const QString& 旧文本, const QString& 新文本)
: 文档指针_(文档), 旧文本_(旧文本), 新文本_(新文本) {}
void undo() override {
文档指针_->设置文本(旧文本_);
setText(QString("撤销: 文本修改"));
}
void redo() override {
文档指针_->设置文本(新文本_);
setText(QString("重做: 文本修改"));
}
private:
文档类* 文档指针_;
QString 旧文本_;
QString 新文本_;
};
// 使用示例
QUndoStack* 撤销栈 = new QUndoStack(this);
撤销栈->push(new 我的撤销命令(文档, "旧内容", "新内容"));
7.2 游戏开发:Unity中的命令模式应用
csharp
// Unity C# 中的命令模式
public interface I命令 {
void 执行();
void 撤销();
}
public class 移动命令 : I命令 {
private Transform 目标;
private Vector3 移动方向;
private float 移动距离;
public 移动命令(Transform 目标物体, Vector3 方向, float 距离) {
目标 = 目标物体;
移动方向 = 方向;
移动距离 = 距离;
}
public void 执行() {
目标.position += 移动方向 * 移动距离;
}
public void 撤销() {
目标.position -= 移动方向 * 移动距离;
}
}
// 命令管理器
public class 命令管理器 : MonoBehaviour {
private Stack<I命令> 命令历史 = new Stack<I命令>();
public void 执行命令(I命令 命令) {
命令.执行();
命令历史.Push(命令);
}
public void 撤销命令() {
if (命令历史.Count > 0) {
I命令 命令 = 命令历史.Pop();
命令.撤销();
}
}
}
第八章:命令模式的未来展望
8.1 微服务架构:命令查询职责分离(CQRS)
在现代微服务架构中,命令模式演变成了CQRS模式:
客户端 命令端 查询端 命令处理器 领域模型 事件存储 查询数据库 查询结果
8.2 事件溯源:永久的操作记录
事件溯源是命令模式的终极形态:
- 每个命令产生一个事件
- 所有事件被永久存储
- 可以通过重放事件重建任何时间点的状态
cpp
// 事件溯源示例
class 银行账户 {
private:
std::vector<std::unique_ptr<账户事件>> 事件流_;
int 当前余额_ = 0;
public:
void 执行命令(std::unique_ptr<账户命令> 命令) {
auto 事件 = 命令->执行(当前余额_);
应用事件(事件.get());
事件流_.push_back(std::move(事件));
}
void 应用事件(账户事件* 事件) {
// 根据事件更新状态
当前余额_ = 事件->应用到(当前余额_);
}
// 从事件流重建状态
void 从事件流重建() {
当前余额_ = 0;
for (auto& 事件 : 事件流_) {
应用事件(事件.get());
}
}
};
总结:命令模式的魔法启示
命令模式就像软件工程中的"时间魔法",它给了我们三个超能力:
🎯 核心价值
- 解耦的智慧:让请求发送者和接收者各自独立,系统更灵活
- 时间的掌控:完美支持撤销/重做,再也不怕手滑
- 组合的威力:宏命令让复杂操作变得简单
🚀 实用建议
- 适合场景:需要撤销/重做、任务队列、操作日志的系统
- 避免过度:简单操作不要强行用命令模式
- 性能考量:注意命令对象的创建和内存使用
🌟 未来展望
命令模式从简单的GUI撤销功能,发展到现代的事件溯源、CQRS架构,证明了其强大的生命力和适应性。随着云原生、微服务架构的普及,命令模式的思想将在分布式系统中继续发挥重要作用。
最后的小故事:还记得开头的小明吗?他学习了命令模式后,不仅实现了完美的撤销功能,还把整个编辑器的架构重构得更加灵活。现在,他再也不用担心误操作了,甚至还能给用户提供操作历史记录功能。最重要的是------他终于可以安心睡觉了!
命令模式就是这样一种魔法:它不仅能解决技术问题,还能给程序员带来心灵的宁静。希望这次奇妙的命令模式之旅,能让你真正爱上这个强大的设计模式!
<摘要>
命令模式就像软件世界的"时间魔法",通过将操作封装成对象,实现了发送者和接收者的解耦,完美支持撤销/重做、操作日志、任务队列等高级功能。本文通过生动的故事和丰富的实例,从文本编辑器到智能家居,全面展示了命令模式的强大威力和实际应用。
<解析>
本文以轻松幽默的讲故事方式,深入浅出地解析了命令模式的各个方面。通过"魔法咒语"的比喻、丰富的代码实例、直观的图表展示,让读者在愉快的阅读体验中掌握命令模式的核心概念和实践技巧。从基础实现到高级应用,从性能优化到现代框架集成,全面覆盖了命令模式的知识体系。