什么是备忘录模式?它解决了什么问题?
简化版
备忘录模式是在不破坏对象封装的前提下,保存对象某一时刻的内部状态,并在需要时恢复。它常用于撤销、回滚、草稿、游戏存档、事务失败恢复等场景。
详细版
备忘录模式的核心是“保存快照,必要时恢复”。
典型角色:
- Originator:原发器,拥有内部状态,负责创建和恢复备忘录。
- Memento:备忘录,保存原发器某一刻的状态。
- Caretaker:负责人,只保存备忘录,不直接修改备忘录内容。
它解决的问题是:外部对象想保存和恢复 Originator 的状态,但又不应该直接访问 Originator 的内部细节。
优点是保护封装、支持撤销恢复、职责清晰。缺点是快照可能占用大量内存,状态复杂时还要考虑深拷贝、版本兼容和过期清理。
完整版教学
一、为什么需要备忘录模式
很多系统都有“后悔一步”的需求。编辑器要撤销,游戏要存档,表单要保存草稿,事务执行失败要回滚状态。
最直接的做法是让外部对象保存 Originator 的所有字段。但这样会暴露内部实现,外部对象知道太多细节,封装被破坏。
备忘录模式的做法是:状态由 Originator 自己打包成 Memento,外部只负责保存这个快照,不理解也不修改快照内容。
二、三类角色如何协作
Originator 创建快照:
Memento save() {
return new Memento(this.state);
}
Originator 恢复快照:
void restore(Memento memento) {
this.state = memento.getState();
}
Caretaker 只保存快照列表,例如撤销栈。它不应该深入修改 Memento 内部状态。
三、备忘录模式保护的是什么
它保护的是 Originator 的封装边界。外部可以让对象保存和恢复,但不需要知道对象内部状态由哪些字段组成。
如果以后 Originator 内部字段变化,只要 Memento 和 Originator 自己处理好,Caretaker 不一定需要修改。
这比外部散落保存字段要稳得多。
四、常见误区与工程判断
备忘录模式不是简单克隆对象。它强调保存对象状态时不破坏封装,恢复也由对象自己完成。
工程中使用它前要评估快照成本。如果对象很大、状态变化频繁,完整快照会占用很多内存。可以考虑增量快照、命令日志、限制撤销步数或压缩存储。
五、备忘录模式真正保护的是“恢复能力”
备忘录模式看起来是在保存对象状态,但它的设计重点不是“存数据”,而是让对象在未来可以安全地恢复到某个历史状态。这个“安全”包含两层含义:外部对象可以保存快照,但不能随便篡改快照内容;原发器可以根据快照恢复自己,但恢复逻辑仍然掌握在原发器内部。
面试官如果追问“为什么不直接把对象复制一份”,可以这样答:简单复制只解决保存问题,不一定解决封装、恢复语义、历史管理和快照粒度问题。备忘录模式把快照对象作为专门角色,让保存、恢复、管理历史三件事有清晰边界。
真实工程里,备忘录模式常和编辑器撤销、游戏存档、表单草稿、流程状态回滚一起出现。判断是否适合它,要看系统是否需要“回到过去某个状态”,以及这个状态是否应该由对象自己负责恢复。
六、用工程约束检验答案
假设编辑器每次修改都保存标题、正文、光标 3 项状态,Caretaker 只持有不透明 Memento;恢复时仍由 Editor 校验并写回。这样即便历史栈保留 50 步,也不会让历史管理器直接操纵编辑器内部字段。
| 检查项 | 核心判断 | 工程含义 |
|---|---|---|
| Originator | 创建与恢复自身快照 | 掌握状态语义 |
| Memento | 封装某一时刻状态 | 对 Caretaker 不透明 |
| Caretaker | 保存、选择、淘汰快照 | 不解释内部字段 |
把关键关系压缩成一条可复述的路径:
editor.createMemento()
-> history.push(snapshot)
history.pop()
-> editor.restore(snapshot)
备忘录保护的是带封装边界的恢复能力,不是给外部提供一份可随意编辑的数据副本。
落地前可以再按下面 3 步复核:
- 先说明“Originator”的核心机制:创建与恢复自身快照;再交代边界:掌握状态语义。
- 接着分析“Memento”:封装某一时刻状态;不能遗漏对应代价或结果:对 Caretaker 不透明。
- 最后用“Caretaker”检查方案:保存、选择、淘汰快照;验收时确认不解释内部字段。
这三项构成完整判断链:先讲清Originator,再说明Memento,最后用Caretaker检验实现是否越界。
面试中若能给出违反“不解释内部字段”的反例,再说明修正办法,答案就从模式定义落到了可验证的工程决策。
七、常见误区与追问
- 误区:只看到“Originator”就认为方案成立。 必须同时说明核心机制“创建与恢复自身快照”和工程边界“掌握状态语义”。
- 误区:把“Memento”当成无条件结论。 只有在“封装某一时刻状态”成立时,才能据此讨论“对 Caretaker 不透明”。
- 追问:备忘录等于序列化吗? 不等于,序列化是存储手段,备忘录强调角色和封装边界。
- 追问:Caretaker 能读取状态吗? 经典意图是不让它解释或修改状态,只管理快照生命周期。
- 追问:恢复是否一定成功? 不一定,原发器可校验版本、依赖和不变量后拒绝无效快照。
- 追问:快照必须包含全部字段吗? 只需包含恢复目标状态所需的数据,但遗漏关联状态会导致不一致。
- 追问:它与数据库事务回滚相同吗? 不同,事务由存储系统保证原子性,备忘录是对象级设计模式。
八、加强记忆
记忆时抓住这条主线:备忘录模式保存对象状态快照;Originator 负责创建和恢复快照;Caretaker 只保管快照,不理解内部状态;它适合撤销、回滚、草稿和存档场景。面试回答先给出模式意图,再用调用链或数据流说明角色协作,最后主动交代适用边界与工程代价。