设计模式 36:快照如何限制内存与别名
编辑报价方案时,需要恢复前一次商品清单。若把可写 List 当成“快照”存进历史,调用方随后增加一项,旧快照也会变;若每次都无限制保存完整清单,历史又会无限增长。保存状态不等于保留一个别名,还需要决定恢复资格和内存边界。
状态由原对象保存,历史只管凭据
labs/36 的 Before.save() 直接返回内部 List,测试把新商品加进调用方输入后,“旧快照”也被修改。After 的内部清单经 List.copyOf 防御性复制;Snapshot 只有私有字段和私有构造器,不给调用方可写状态。History.save(editor) 保存由原对象创建的不透明快照、只保留最新两份;恢复前确认它属于当前编辑器且仍在这份历史中,否则拒绝。测试还断言修改原输入不会改变编辑器、已过期快照不能恢复且拒绝时当前清单不变。
1 | |
这里的 Memento 意图是让恢复旧状态无需把编辑器内部可写表示交给历史管理方;普通查询 items() 仍返回只读清单值,不能据此说所有数据都被隐藏。Alternative.copy(items) 为只需一次“返回上一版”的情形直接复制列表,更少对象和生命周期规则;不必为每份可复制数据设一个 Caretaker。最大保留 2 是历史内部上限,外部调用方若长期持有 Snapshot,其对应列表和用于验证归属的编辑器引用仍会占内存;测试没有测实际堆内存和垃圾回收。快照不持久化、没有跨进程恢复能力。
| 方案 | 外部修改原清单 | 过期快照与空间成本 |
|---|---|---|
Before |
所谓快照一起变 | 别名导致历史不可靠 |
After |
已存值不变 | 仅接受同编辑器的最近两份历史快照 |
Alternative |
显式副本不变 | 由调用方自行决定保存多少副本 |
flowchart LR
Caller[编辑调用方] --> History[History]
History --> Editor[After]
Editor --> Snapshot[Snapshot owner 和 items]
History --> Bounded[有上限的 snapshots]
Bounded --> Snapshot
History -.校验归属与保留后恢复.-> Editor
验证与练习
在 examples/design-patterns/ 执行 ./mvnw -B -ntp -pl labs/36 -am test 与累计 ./mvnw -B -ntp verify,实际环境、输入、原始输出和退出码见 examples/design-patterns/evidence/36/RUN.md。负例是旧版别名;边界是上限 0 拒绝、过期/跨编辑器恢复失败。
- 新增“只允许恢复最近一份快照”,先写旧内容恢复合同和更早快照拒绝的新断言,再改历史上限并复跑别名隔离测试。
- 如果编辑器数据始终只有一个短字符串,删除
History改用直接值复制;通过断言比较额外历史对象是否仍有需求。
参考资料
- GoF 原书公开图书馆 PDF,5.6 Memento,目录页标注起始页 316:https://cpcc.chd.gov.in/Content/PDFs/w8UkV3tWNyEtsbUZSWJ7fVhuB9A3tsGYdm4w6VGzg2wUTNFYikqnvvFbbkiW2zmfspPEghd7QTamiMby3lVIBemrhdVWwt6rOQnm.pdf 。
- Memento 模式简定义,InfoWorld/JavaWorld:https://www.infoworld.com/article/2170730/design-patterns-the-big-picture-part-1-design-pattern-history-and-classification.html 。
- 本篇来源边界:
writing-plans/design-patterns/SOURCES.md。 - 实验代码:
examples/design-patterns/labs/36/。
上一节:35 协作集中会形成大对象吗;下一节:37 新操作与新节点哪个更贵。






