第一部分:模式基础与实现
1. 模式定义
备忘录模式(Memento Pattern)是一种行为设计模式,它允许在不破坏封装性的前提下,捕获并外部化一个对象的内部状态,以便在将来某个时刻可以将该对象恢复到原先保存的状态。
2. 核心角色
角色说明:
- Originator(发起人):需要保存状态的对象
- Memento(备忘录):存储Originator内部状态的对象
- Caretaker(管理者):负责保存和管理备忘录
3. 应用场景
| 场景 | 说明 | 典型案例 |
|---|---|---|
| 撤销操作 | 需要回滚到之前状态 | Ctrl+Z、编辑器撤销 |
| 保存快照 | 定期保存状态用于恢复 | 游戏存档、虚拟机快照 |
| 事务回滚 | 数据库或业务操作失败回滚 | 银行转账、订单操作 |
| 历史记录 | 记录操作历史用于回溯 | 浏览器后退按钮 |
4. 完整C++代码示例
#include <iostream>
#include <string>
#include <vector>
#include <memory>
// 备忘录类 - 存储状态
class Memento {
private:
std::string state;
int version;
public:
Memento(const std::string& state, int version)
: state(state), version(version) {}
std::string getState() const { return state; }
int getVersion() const { return version; }
// 仅允许Originator访问状态设置(友元类)
friend class TextEditor;
};
// 发起人类 - 文本编辑器
class TextEditor {
private:
std::string content;
std::string cursorPosition;
int fontSize;
public:
TextEditor() : fontSize(12) {}
void write(const std::string& text) {
content += text;
}
void setCursor(const std::string& pos) {
cursorPosition = pos;
}
void setFontSize(int size) {
fontSize = size;
}
// 创建备忘录(保存快照)
std::unique_ptr<Memento> createMemento() {
std::string fullState = content + "|" + cursorPosition + "|" + std::to_string(fontSize);
return std::make_unique<Memento>(fullState, getVersion());
}
// 从备忘录恢复
void restoreFromMemento(const Memento& memento) {
std::string state = memento.getState();
size_t pos1 = state.find('|');
size_t pos2 = state.find('|', pos1 + 1);
content = state.substr(0, pos1);
cursorPosition = state.substr(pos1 + 1, pos2 - pos1 - 1);
fontSize = std::stoi(state.substr(pos2 + 1));
std::cout << "恢复版本 " << memento.getVersion() << std::endl;
}
void display() {
std::cout << "--- 编辑器状态 ---" << std::endl;
std::cout << "内容: " << content << std::endl;
std::cout << "光标: " << cursorPosition << std::endl;
std::cout << "字号: " << fontSize << std::endl;
std::cout << "----------------" << std::endl;
}
private:
int getVersion() const {
static int counter = 0;
return ++counter;
}
};
// 管理人类 - 历史记录管理器
class HistoryManager {
private:
std::vector<std::unique_ptr<Memento>> history;
int currentIndex = -1;
public:
void save(TextEditor& editor) {
// 删除当前索引之后的所有记录(实现撤销后的新分支)
history.resize(currentIndex + 1);
history.push_back(editor.createMemento());
currentIndex++;
std::cout << "保存状态,当前版本: " << currentIndex + 1 << std::endl;
}
void undo(TextEditor& editor) {
if (currentIndex > 0) {
currentIndex--;
editor.restoreFromMemento(*history[currentIndex]);
std::cout << "撤销成功" << std::endl;
} else {
std::cout << "无法撤销:已经是最早版本" << std::endl;
}
}
void redo(TextEditor& editor) {
if (currentIndex + 1 < history.size()) {
currentIndex++;
editor.restoreFromMemento(*history[currentIndex]);
std::cout << "重做成功" << std::endl;
} else {
std::cout << "无法重做:已经是最新版本" << std::endl;
}
}
void showHistory() {
std::cout << "\n历史版本记录:" << std::endl;
for (size_t i = 0; i < history.size(); i++) {
std::cout << " 版本 " << (i + 1) << ": "
<< history[i]->getState() << std::endl;
}
}
};
// 客户端代码
int main() {
TextEditor editor;
HistoryManager history;
// 初始状态
editor.write("Hello ");
editor.setCursor("第1行第6列");
editor.setFontSize(12);
history.save(editor);
// 修改1
editor.write("World!");
editor.setCursor("第1行第12列");
history.save(editor);
// 修改2
editor.setFontSize(16);
editor.write(" Welcome to Memento!");
editor.setCursor("第1行第32列");
history.save(editor);
editor.display();
// 撤销两次
history.undo(editor);
editor.display();
history.undo(editor);
editor.display();
// 重做一次
history.redo(editor);
editor.display();
history.showHistory();
return 0;
}
5. 运行结果
保存状态,当前版本: 1
保存状态,当前版本: 2
保存状态,当前版本: 3
--- 编辑器状态 ---
内容: Hello World! Welcome to Memento!
光标: 第1行第32列
字号: 16
----------------
恢复版本 2
撤销成功
--- 编辑器状态 ---
内容: Hello World!
光标: 第1行第12列
字号: 12
----------------
恢复版本 1
撤销成功
--- 编辑器状态 ---
内容: Hello
光标: 第1行第6列
字号: 12
----------------
恢复版本 2
重做成功
--- 编辑器状态 ---
内容: Hello World!
光标: 第1行第12列
字号: 12
----------------
历史版本记录:
版本 1: Hello |第1行第6列|12
版本 2: Hello World!|第1行第12列|12
版本 3: Hello World! Welcome to Memento!|第1行第32列|16
6. 优缺点
优点:
- ✅ 保持封装边界:不暴露Originator内部细节
- ✅ 简化Originator:不需要管理状态历史
- ✅ 灵活的状态管理:支持撤销/重做功能
缺点:
- ❌ 内存消耗:大量备忘录可能占用较多内存
- ❌ 性能开销:频繁保存状态影响性能
- ❌ 管理复杂:需要实现完整的生命周期管理
7. 优化建议
// 优化1:限制历史记录数量
class LimitedHistoryManager {
static constexpr size_t MAX_HISTORY = 50;
std::vector<std::unique_ptr<Memento>> history;
};
// 优化2:支持增量保存
class DeltaMemento {
std::vector<std::pair<std::string, std::string>> changes;
};
第二部分:核心思想与深度思维解析
一、核心思想:三个维度的哲学思考
二、深度思维解析
2.1 "记忆外置"思维
核心洞察: 将"记忆"从"主体"中分离出来,就像人类可以写下日记来记录状态。
// 反面教材:状态管理混杂在业务逻辑中
class BadEditor {
std::vector<std::string> history; // 状态和历史混杂
std::string content;
void badUndo() {
// 业务逻辑中混杂状态管理
content = history.back(); // 破坏了封装
}
};
// 正面案例:记忆外置
class GoodEditor {
std::string content;
// 只负责自己的业务,将记忆交给专门的管理者
Memento* save() { return new Memento(content); }
void restore(Memento* m) { content = m->getContent(); }
};
class Memento {
std::string snapshot; // 纯粹的记忆载体
};
思维启示:
- 不要试图让一个对象既处理业务又管理历史
- 记忆是一种可以分离的独立实体
- 将职责分解,每个部分只做一件事
2.2 "时间胶囊"思维
将对象的不同时刻状态封装成一个个"时间胶囊",可以随时打开任何一个。
// 时间胶囊的实现思维
class TimeCapsule {
struct Snapshot {
std::chrono::system_clock::time_point timestamp;
std::string state;
std::string description; // 为状态添加语义
};
std::vector<Snapshot> capsules;
public:
// 就像把当前时刻装进瓶子
void buryCapsule(const std::string& state, const std::string& desc) {
capsules.push_back({std::chrono::system_clock::now(), state, desc});
}
// 打开某个时间点的瓶子
std::string openCapsule(int index) {
return capsules[index].state;
}
};
2.3 "黑盒封装"思维
备忘录模式最精妙之处:让对象主动吐出状态,而不是被动被读取状态。
思维对比:
// ❌ 错误思维:外部窥探模式
class BrokenMementoPattern {
class Originator {
public:
std::string state; // 公开状态,破坏封装
};
class Caretaker {
void save(Originator& o) {
// 直接读取内部状态
memento.state = o.state; // 耦合太强
}
};
};
// ✅ 正确思维:自主生成模式
class CorrectMementoPattern {
class Originator {
private:
std::string secret; // 完全隐藏
public:
// 主动"吐出"备忘录
Memento* createMemento() {
return new Memento(secret); // 自己打包状态
}
// 主动"吞入"备忘录
void restore(Memento* m) {
secret = m->getSecret(); // 自己解包
}
};
class Memento {
std::string secret; // 只有Originator能访问
friend class Originator; // 关键:使用友元控制访问
};
};
三、类比思维:现实世界的映射
| 现实世界 | 编程世界 | 思维要点 |
|---|---|---|
| 游戏存档 | Memento | 保存的是完整状态快照 |
| 保险柜 | 备忘录对象 | 保护内部信息不被乱改 |
| 历史博物馆 | Caretaker | 保管文物但不解读内容 |
| 时间机器 | 恢复操作 | 回到过去的某个状态 |
// 类比:游戏存档系统
class GamePlayer {
int level;
int health;
std::vector<std::string> items;
// 就像在游戏菜单中"保存"
SaveFile* quickSave() {
return new SaveFile(level, health, items);
}
// 就像"读取"存档
void quickLoad(SaveFile* save) {
this->level = save->level;
this->health = save->health;
this->items = save->items;
}
};
四、模式思维的演进路径
思维演进过程:
-
初级阶段:直接在对象中保存状态
- 缺点:对象又当运动员又当裁判
-
中级阶段:外部对象读取状态保存
- 缺点:需要暴露内部细节
-
高级阶段:对象主动吐出状态胶囊
- 优点:职责清晰,封装完美
五、思维的局限性反思
并非所有场景都适用备忘录模式:
// 场景1:状态太庞大
class HugeState {
char pixels[1920*1080*4]; // 8MB图像数据
// 频繁保存会消耗大量内存
};
// 场景2:状态变化太频繁
class RapidChange {
// 每秒变化1000次的状态
// 备忘录模式会造成性能瓶颈
};
// 替代思维:使用命令模式 + 增量存储
class CommandPattern {
std::vector<Command> commands; // 只存储操作,不存储状态
void undo() {
// 反向执行命令,而不是恢复整个状态
}
};
六、思维的本质总结
备忘录模式的思维精髓可以概括为:
“把对象的记忆从对象身上剥离出来,成为一个独立的、可操作的时间胶囊,让对象可以轻松地进行时空跳跃,同时保持自身的纯粹性和封装性。”
四个核心思维维度:
- 分离思维:状态 = 记忆,对象 = 主体,二者分离
- 胶囊思维:将状态打包成不可变的时间胶囊
- 代理思维:备忘录作为状态的代理,控制访问权限
- 时间线思维:将对象的生命周期视为可以回溯的时间线
这种思维模式教会我们:在软件设计中,学会"分离关注点"往往比学会"集成功能"更重要。备忘录模式就是一个完美的"分离"案例。
第三部分:总结
备忘录模式通过将状态存储与业务逻辑分离,既保持了封装性,又提供了灵活的状态管理能力。理解其背后的"记忆外置"、"时间胶囊"和"黑盒封装"三种思维,能够帮助我们更好地在合适的场景应用这一模式。实际开发中,备忘录模式常与命令模式结合实现完整的撤销系统,也可以根据具体需求进行优化(如限制历史记录数量、使用增量存储等)。

837

被折叠的 条评论
为什么被折叠?



