备忘录模式解决什么
备忘录模式(Memento)在不破坏封装的前提下捕获对象内部状态,并在需要时把对象恢复到该状态。它让撤销、快照与回滚成为可能,代价是状态副本占用资源。使用时有两个约束:快照本身应当不可变,避免后续操作把快照内容改掉;快照数量要有上限与淘汰策略,否则内存会随操作次数持续增长。
三个角色
| 原发器 | 持有可变化的内部状态,负责产生与恢复快照 |
|---|---|
| 快照 | 状态副本,应当不可变,不对外暴露内部引用 |
| 保管者 | 负责保存快照序列,不解读快照内容 |
| 常见变体 | 增量快照、命令与快照组合实现撤销重做 |
典型使用场景
- 文本编辑器撤销:每次编辑前保存光标与文本状态,撤销时回到上一快照
- 配置变更回滚:改动前保存基准配置,验证失败后一键恢复
- 批量导入预备份:导入前记录关键记录状态,异常时回滚到导入前
- 工作流引擎:环节推进时保存上下文,失败重试从最近检查点继续
- 交互式工具:支持撤销重做的图形或数据编辑场景
基本形态
基本形态
class Editor {
private String text;
private int cursor;
Memento save() { // 产生不可变快照
return new Memento(text, cursor);
}
void restore(Memento m) { // 从快照恢复
this.text = m.text();
this.cursor = m.cursor();
}
}实现要点对照
| 要点 | 正确做法 | 常见错误 |
|---|---|---|
| 快照内容 | 保存值或不可变副本 | 直接持有原对象引用,后续修改污染快照 |
| 快照数量 | 设定上限并按策略淘汰 | 无限追加,长时运行内存持续增长 |
| 暴露程度 | 只允许原发器解读内容 | 把内部列表直接暴露,调用方可随意篡改 |
| 序列化 | 按需选择内存快照或序列化存储 | 一律序列化,小改动也付出高成本 |
| 并发修改 | 快照期间锁定或复制 | 恢复时发现状态已被其他线程改变 |
快照粒度决定可用性与成本:全量快照实现简单但占用大,增量快照省内存但恢复逻辑复杂且容易出错。选择时看状态规模与撤销频率,不要先上最复杂的方案。
撤销栈与快照列表通常需要上限:常见做法是限定步数(例如保留最近五十步)或按内存占用淘汰。没有上限的实现往往在长时间使用后出现内存问题,而这类问题不易在测试中暴露。
落地检查清单
- 快照不可变,恢复过程不会反过来修改快照
- 快照保存的是值或副本,而不是可变对象的引用
- 快照数量有上限与淘汰策略
- 恢复后对象内部一致性得到保证,关联结构同步还原