设计模式(C++)详解——备忘录模式(1)
备忘录模式:时光倒流的魔法秘籍 ✨
想象一下,如果生活中有一个"撤销"按钮该多好!备忘录模式就是编程世界里的时光机,让我们一起来探索这个神奇的设计模式吧!
🎯 模式初印象:什么是备忘录?
备忘录模式就像是我们生活中的后悔药 💊!它允许我们在不暴露对象内部细节的情况下,保存和恢复对象的状态。
核心思想:偷偷拍下对象的"快照" 📸,等到需要的时候再恢复到那个美好的瞬间!
// 举个栗子:文本编辑器的撤销功能
你输入:Hello World!
然后删除,变成了:Hello
突然想:"哎呀,我为什么要删掉World!"
点击撤销 → 回到:Hello World!
这就是备忘录模式的魔力!
🏗️ 三大主角登场
1. Originator(发起人)
角色:需要被保存状态的对象
任务:创建备忘录 + 从备忘录恢复
2. Memento(备忘录)
角色:状态存储箱
任务:安全地保存发起人的内部状态
3. Caretaker(管理者)
角色:备忘录的保管员
任务:保存和管理备忘录,但不能查看或操作内容
🔍 深入原理:如何实现时光倒流?
UML类图大揭秘
C++实现:文本编辑器案例
#include <iostream>
#include <string>
#include <vector>
#include <memory>/*** @brief 备忘录类 - 时光胶囊* * 存储文本编辑器的状态,就像给文本拍了一张照片* 注意:为了封装性,我们使用友元类,但要谨慎使用!*/
class TextMemento {
private:std::string content_; // 保存的文本内容// 只有TextEditor可以创建和访问备忘录friend class TextEditor;/*** @brief 构造函数 - 保存状态* * @param content 要保存的文本内容*/TextMemento(const std::string& content) : content_(content) {}/*** @brief 获取保存的状态* * @return std::string 保存的文本内容*/std::string getContent() const {return content_;}
};/*** @brief 发起人类 - 文本编辑器* * 可以创建备忘录保存当前状态,也可以从备忘录恢复状态*/
class TextEditor {
private:std::string content_; // 当前文本内容public:/*** @brief 设置文本内容* * @param text 新的文本内容*/void write(const std::string& text) {content_ = text;std::cout << "📝 当前文本: " << content_ << std::endl;}/*** @brief 获取当前文本* * @return std::string 当前文本内容*/std::string getContent() const {return content_;}/*** @brief 创建备忘录 - 拍快照!* * @return std::shared_ptr<TextMemento> 包含当前状态的备忘录*/std::shared_ptr<TextMemento> save() {std::cout << "💾 保存状态: " << content_ << std::endl;return std::make_shared<TextMemento>(content_);}/*** @brief 从备忘录恢复 - 时光倒流!* * @param memento 要恢复的备忘录*/void restore(std::shared_ptr<TextMemento> memento) {content_ = memento->getContent();std::cout << "🔄 恢复状态: " << content_ << std::endl;}
};/*** @brief 管理者类 - 历史记录管理器* * 负责保存和管理所有的备忘录,但不能查看内容* 就像是一个安全的保险箱 🔒*/
class HistoryManager {
private:std::vector<std::shared_ptr<TextMemento>> history_; // 历史记录栈public:/*** @brief 保存备忘录到历史记录* * @param memento 要保存的备忘录*/void push(std::shared_ptr<TextMemento> memento) {history_.push_back(memento);std::cout << "📚 已保存到历史记录,当前共有 " << history_.size() << " 个记录" << std::endl;}/*** @brief 从历史记录中弹出最新的备忘录* * @return std::shared_ptr<TextMemento> 最新的备忘录*/std::shared_ptr<TextMemento> pop() {if (history_.empty()) {std::cout << "❌ 历史记录为空!" << std::endl;return nullptr;}auto memento = history_.back();history_.pop_back();std::cout << "⏮️ 从历史记录恢复,剩余 " << history_.size() << " 个记录" << std::endl;return memento;}/*** @brief 检查是否有历史记录* * @return bool 是否有记录*/bool hasHistory() const {return !history_.empty();}
};// 演示代码:让我们看看备忘录模式的威力!
int main() {std::cout << "🎬 开始文本编辑器演示..." << std::endl;// 创建文本编辑器和历史管理器TextEditor editor;HistoryManager history;// 第一次编辑并保存std::cout << "\n--- 第一次编辑 ---" << std::endl;editor.write("Hello, ");history.push(editor.save());// 第二次编辑并保存std::cout << "\n--- 第二次编辑 ---" << std::endl;editor.write("Hello, World!");history.push(editor.save());// 第三次编辑(不保存)std::cout << "\n--- 第三次编辑(错误的编辑) ---" << std::endl;editor.write("Hello, Wrold!"); // 拼写错误!// 哎呀,拼写错了!撤销到上一个状态std::cout << "\n--- 第一次撤销 ---" << std::endl;auto lastState = history.pop();if (lastState) {editor.restore(lastState);}// 再撤销一次std::cout << "\n--- 第二次撤销 ---" << std::endl;lastState = history.pop();if (lastState) {editor.restore(lastState);}// 尝试再次撤销(应该没有记录了)std::cout << "\n--- 尝试第三次撤销 ---" << std::endl;lastState = history.pop();if (!lastState) {std::cout << "无法撤销,已经没有历史记录了!" << std::endl;}return 0;
}
🎨 更多精彩案例
案例1:游戏存档系统 🎮
#include <iostream>
#include <vector>
#include <memory>/*** @brief 游戏角色备忘录*/
class GameMemento {
private:int level_;int health_;std::string position_;friend class GameCharacter;GameMemento(int level, int health, const std::string& pos): level_(level), health_(health), position_(pos) {}int getLevel() const { return level_; }int getHealth() const { return health_; }std::string getPosition() const { return position_; }
};/*** @brief 游戏角色*/
class GameCharacter {
private:int level_ = 1;int health_ = 100;std::string position_ = "起始点";public:void levelUp() {level_++;health_ = 100; // 升级回满血std::cout << "🎉 升级到 " << level_ << " 级!" << std::endl;}void takeDamage(int damage) {health_ -= damage;std::cout << "💔 受到 " << damage << " 点伤害,剩余生命: " << health_ << std::endl;}void moveTo(const std::string& position) {position_ = position;std::cout << "🚶 移动到: " << position_ << std::endl;}void displayStatus() const {std::cout << "📊 状态 - 等级:" << level_ << " 生命:" << health_ << " 位置:" << position_ << std::endl;}std::shared_ptr<GameMemento> save() {std::cout << "💾 游戏已存档" << std::endl;return std::make_shared<GameMemento>(level_, health_, position_);}void load(std::shared_ptr<GameMemento> memento) {level_ = memento->getLevel();health_ = memento->getHealth();position_ = memento->getPosition();std::cout << "🔄 游戏已读档" << std::endl;displayStatus();}
};// 游戏存档管理器
class SaveManager {
private:std::vector<std::shared_ptr<GameMemento>> saves_;public:void quickSave(std::shared_ptr<GameMemento> save) {saves_.push_back(save);std::cout << "⚡ 快速存档完成" << std::endl;}std::shared_ptr<GameMemento> quickLoad() {if (saves_.empty()) {std::cout << "❌ 没有存档!" << std::endl;return nullptr;}auto save = saves_.back();std::cout << "⚡ 快速读档完成" << std::endl;return save;}
};
案例2:绘图程序的历史记录 🎨
#include <iostream>
#include <vector>
#include <memory>/*** @brief 绘图状态备忘录*/
class DrawingMemento {
private:std::vector<std::string> shapes_;friend class DrawingCanvas;DrawingMemento(const std::vector<std::string>& shapes) : shapes_(shapes) {}std::vector<std::string> getShapes() const {return shapes_;}
};/*** @brief 绘图画布*/
class DrawingCanvas {
private:std::vector<std::string> shapes_;public:void drawShape(const std::string& shape) {shapes_.push_back(shape);std::cout << "✏️ 绘制: " << shape << std::endl;displayCanvas();}void displayCanvas() const {std::cout << "🎨 画布内容: ";for (const auto& shape : shapes_) {std::cout << shape << " ";}std::cout << std::endl;}std::shared_ptr<DrawingMemento> save() {return std::make_shared<DrawingMemento>(shapes_);}void restore(std::shared_ptr<DrawingMemento> memento) {shapes_ = memento->getShapes();std::cout << "🔄 恢复画布状态" << std::endl;displayCanvas();}
};
🔧 实现要点与技巧
封装性的保护神
备忘录模式最大的挑战就是如何在不破坏封装性的前提下保存状态。我们有几种解决方案:
方法 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
友元类 | 简单直接 | 破坏封装性 | 小型项目 |
内部类 | 较好的封装 | C++支持有限 | 推荐使用 |
接口隔离 | 完全封装 | 实现复杂 | 大型项目 |
性能考虑 ⚡
内存使用:备忘录会占用额外内存,特别是状态很大时
// 优化技巧:增量保存
class IncrementalMemento {// 只保存变化的部分,而不是整个状态
};
深拷贝 vs 浅拷贝:
// 深拷贝 - 安全但耗时
TextMemento(const std::string& content) : content_(content) {}// 如果需要优化,可以考虑共享不变的部分
🎯 适用场景大盘点
✅ 强烈推荐使用
-
撤销/重做功能 📝
- 文本编辑器、绘图软件、IDE
-
游戏存档 🎮
- 角色状态、游戏进度、场景状态
-
事务回滚 💰
- 数据库操作、金融交易
-
配置保存 ⚙️
- 软件设置、用户偏好
❌ 谨慎使用的情况
-
状态太大 🐘
- 如果对象状态非常庞大,频繁保存可能内存爆炸
-
性能敏感 ⏱️
- 实时系统、高频交易
-
简单场景 🐛
- 如果用不上撤销功能,就别过度设计
🆚 与其他模式的对比
模式 | 关系 | 区别 |
---|---|---|
命令模式 | 好搭档 | 命令模式记录操作,备忘录模式记录状态 |
原型模式 | 类似 | 原型模式克隆整个对象,备忘录只保存状态 |
状态模式 | 互补 | 状态模式管理行为,备忘录模式保存状态 |
💡 最佳实践总结
-
封装性是王道 👑
- 确保只有Originator能访问Memento的内部
-
考虑内存使用 🧠
- 大状态对象考虑增量保存或压缩
-
管理生命周期 ⏳
- 及时清理不再需要的备忘录
-
提供友好接口 🤝
- 让Caretaker容易使用,但看不到内部
🎉 总结
备忘录模式就像编程世界里的时间魔法 ⏰!它让我们能够:
- ✅ 保存对象状态而不破坏封装
- ✅ 实现强大的撤销/重做功能
- ✅ 支持事务回滚和存档功能
- ✅ 让代码更加灵活和健壮
记住这个模式的精髓:偷偷保存,安全恢复!就像玩游戏时及时存档,遇到困难时从容读档一样 🎮。
下次当你需要"撤销"功能时,别忘了备忘录模式这个得力助手!它会让你的程序像有了超能力一样强大 💪!
备忘录模式口诀:
状态保存要封装,备忘录来帮忙忙
发起人,管理者,各司其职不越界
撤销重做真方便,程序健壮又灵活! 🚀