设计模式(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容易使用,但看不到内部
总结
备忘录模式就像编程世界里的时间魔法 ⏰!它让我们能够:
- ✅ 保存对象状态而不破坏封装
- ✅ 实现强大的撤销/重做功能
- ✅ 支持事务回滚和存档功能
- ✅ 让代码更加灵活和健壮
记住这个模式的精髓:偷偷保存,安全恢复!就像玩游戏时及时存档,遇到困难时从容读档一样 。
下次当你需要"撤销"功能时,别忘了备忘录模式这个得力助手!它会让你的程序像有了超能力一样强大 !
备忘录模式口诀:
状态保存要封装,备忘录来帮忙忙
发起人,管理者,各司其职不越界
撤销重做真方便,程序健壮又灵活!

浙公网安备 33010602011771号