← 返回题目列表

备忘录模式中快照应该深拷贝还是浅拷贝?

高频 困难 第 15 / 25 题 更新于 2026/07/28
备忘录模式深拷贝浅拷贝快照一致性

简化版

备忘录快照通常需要保证恢复时状态不被后续修改污染。如果状态包含可变对象,浅拷贝可能导致历史快照被当前对象改动影响;深拷贝更安全但成本更高。实际选择要看状态是否可变、对象大小和性能要求。

详细版

浅拷贝:

  • 只复制引用。
  • 成本低。
  • 可变引用会共享,历史快照可能被污染。

深拷贝:

  • 复制完整对象图。
  • 快照独立性强。
  • 成本高,复杂对象处理困难。

判断方式:

  • 状态都是不可变值:浅拷贝通常可行。
  • 状态包含可变集合、对象:需要深拷贝或不可变结构。
  • 状态很大:考虑增量快照、结构共享、操作日志。

快照正确性比代码简短更重要。

完整版教学

一、浅拷贝的问题

假设备忘录保存的是一个 List 引用。如果 Originator 后续继续修改这个 List,备忘录里的 List 也跟着变了。

等到恢复时,你以为拿到的是历史状态,其实已经被当前操作污染。这会让撤销功能看起来随机失效。

二、深拷贝的代价

深拷贝能保证快照独立,但它要复制整个对象图。对象越大,成本越高;对象关系越复杂,越容易遇到循环引用、共享引用和不可复制资源。

例如文件句柄、数据库连接、线程对象就不适合作为快照直接深拷贝。

三、不可变对象和结构共享

如果状态用不可变对象表示,快照可以很轻。因为不可变对象不会被后续修改污染,多个快照可以安全共享未变化部分。

这也是很多编辑器、前端状态管理、持久化数据结构会强调不可变更新的原因。

四、常见误区与工程判断

不要机械回答“一定深拷贝”。如果状态很大、操作频繁,完整深拷贝可能把系统拖垮。

工程中可以分层处理:小状态深拷贝,大状态保存增量,复杂状态使用不可变数据结构或命令日志。关键是保证恢复语义正确。

五、快照正确性的核心是隔离变化

浅拷贝快照最大的风险是共享引用。如果 Originator 的状态里有列表、Map、复杂对象,Memento 只是保存引用,那么后续修改原对象时,历史快照也可能被一起改掉。这样等到撤销时,恢复出来的不是历史状态,而是被污染后的状态。

深拷贝能提升快照隔离性,但代价是性能和内存。对象图越大、层级越深、引用关系越复杂,深拷贝越昂贵,还可能遇到循环引用、不可复制资源、外部连接等问题。因此工程里常见折中:不可变对象直接共享,可变集合做防御性拷贝,大对象使用增量快照或差异记录。

面试时不要机械地说“深拷贝一定好”。更准确的回答是:快照必须保证恢复语义正确,至于是深拷贝、不可变结构、增量记录还是复制关键字段,要根据状态规模和变化频率选择。

面试官如果追问如何验证快照是否正确,可以给一个测试思路:先创建对象并保存快照,再修改原对象里的嵌套集合或子对象,然后执行恢复,检查恢复结果是否等于保存时状态。如果恢复结果受到后续修改影响,就说明快照隔离失败。

六、用工程约束检验答案

判断标准是可变引用是否跨时间共享。快照保存 List<Item> 时只复制 List,却仍共享 5 个可变 Item,修改任意 Item 都会污染历史;若 Item 是不可变值对象,复制容器或使用不可变列表即可满足隔离。

检查项核心判断工程含义
浅拷贝复制顶层引用结构共享可变子对象时不安全
深拷贝复制整个可变状态闭包空间与时间成本高
结构共享共享不可变节点兼顾隔离与空间

把关键关系压缩成一条可复述的路径:

列出状态对象图
标记每个节点是否可变
复制所有可能被修改的路径
用测试验证历史不随当前状态变化

答案不应简单站队深拷贝;快照要求的是时间隔离,深拷贝只是实现它的一种手段。

落地前可以再按下面 3 步复核:

  1. 先说明“浅拷贝”的核心机制:复制顶层引用结构;再交代边界:共享可变子对象时不安全。
  2. 接着分析“深拷贝”:复制整个可变状态闭包;不能遗漏对应代价或结果:空间与时间成本高。
  3. 最后用“结构共享”检查方案:共享不可变节点;验收时确认兼顾隔离与空间。

这三项构成完整判断链:先讲清浅拷贝,再说明深拷贝,最后用结构共享检验实现是否越界。

面试中若能给出违反“兼顾隔离与空间”的反例,再说明修正办法,答案就从模式定义落到了可验证的工程决策。

七、常见误区与追问

  • 误区:只看到“浅拷贝”就认为方案成立。 必须同时说明核心机制“复制顶层引用结构”和工程边界“共享可变子对象时不安全”。
  • 误区:把“深拷贝”当成无条件结论。 只有在“复制整个可变状态闭包”成立时,才能据此讨论“空间与时间成本高”。
  • 追问:String 需要深拷贝吗? 通常不需要,因为 String 不可变,可安全共享。
  • 追问:Collections.unmodifiableList 足够吗? 它只禁止通过该视图改列表,若底层列表或元素可变仍可能污染。
  • 追问:序列化深拷贝推荐吗? 实现方便但性能、类型兼容和安全成本较高,需评估。
  • 追问:循环引用怎么复制? 深拷贝需维护已复制对象映射,避免无限递归并保持共享关系。
  • 追问:怎样证明快照独立? 保存后修改多层子对象,再恢复并断言历史值未变化。

八、加强记忆

记忆时抓住这条主线:可变引用浅拷贝会污染历史快照;深拷贝更安全但成本更高;不可变对象适合做轻量快照;快照策略要按状态规模和变化频率选择。面试回答先给出模式意图,再用调用链或数据流说明角色协作,最后主动交代适用边界与工程代价。