什么是备忘录模式(Memento Pattern)

备忘录模式允许在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
想象一下你在玩一个RPG游戏(对象 Originator),需要在打 Boss 前保存进度。你点击“保存游戏”,系统会创建一个“存档文件”(备忘录 Memento),这个文件包含了你当前角色的所有状态(等级、装备、位置等)。这个存档文件被游戏系统(管理者 Caretaker)保管起来。如果之后你打 Boss 失败了,你可以选择“读取存档”,游戏系统会拿出之前的存档文件,让你的角色恢复到保存时的状态。重要的是,你(或者游戏系统)不需要知道存档文件里具体是怎么存储这些数据的,只需要知道它是一个有效的存档即可。
备忘录模式的核心在于将对象状态的快照存储在一个独立的 Memento 对象中,由一个第三方 Caretaker 来保管,而 Originator 负责创建和恢复状态,同时保持其内部结构的封装性。

核心思想

备忘录模式主要涉及三个角色:
  1. 发起人 (Originator):
      • 这是拥有需要被保存的内部状态的对象。
      • 它创建一个备忘录 (Memento) 对象,用于存储其当前内部状态的一个快照。
      • 它也提供一个方法,接收一个 Memento 对象,用于将自己的内部状态恢复到该 Memento 所记录的状态。
      • 关键: Originator 知道如何读写 Memento 中的状态,但 Memento 的内部结构对其他对象(尤其是 Caretaker)是隐藏的。
  1. 备忘录 (Memento):
      • 负责存储 Originator 对象的内部状态。
      • 它应该防止 Originator 以外的对象访问其内部存储的状态。通常,Memento 对外只提供非常有限的接口(有时甚至没有公共方法),其状态的设置和获取通常由 Originator 通过更宽松的访问权限(如 Kotlin/Java 中的 internal/包私有,或者使用嵌套类)来完成。
      • 理想情况下,Memento 是不可变 (Immutable) 的,或者至少在其创建后状态不应被外部修改。
  1. 管理者 (Caretaker):
      • 负责保管 Memento 对象。它不知道 Memento 内部的状态细节,只知道它是一个可以用来恢复 Originator 状态的“凭证”。
      • Caretaker 从 Originator 获取 Memento 对象,并在需要恢复时将其传递回 Originator。
      • Caretaker 可以存储多个 Memento 对象(例如,使用栈来实现撤销/重做功能)。

优点

  1. 保持封装性 (Preserves Encapsulation): Originator 的内部状态细节不会暴露给外部对象(尤其是 Caretaker),符合封装原则。状态的保存和恢复逻辑都封装在 Originator 内部。
  1. 简化 Originator: Originator 不需要管理其状态的历史版本,这个责任转移给了 Caretaker。Originator 只负责创建和使用 Memento。
  1. 易于实现撤销/重做/状态恢复: 这是该模式最常见的应用场景,提供了一种清晰的机制来保存和恢复对象状态。

可能的缺点

  1. 资源消耗 (Resource Consumption): 如果 Originator 的状态非常庞大,或者需要频繁地创建 Memento,可能会消耗大量的内存。需要考虑 Memento 的存储策略(例如,限制历史记录的数量,只保存状态差异等)。
  1. 可能破坏封装性 (If Not Implemented Carefully): 如果 Memento 的实现允许除了 Originator 之外的对象访问其内部状态,封装性就会被破坏。需要使用语言特性(如访问修饰符、嵌套类、接口隔离)来确保 Memento 的状态对 Caretaker 是不透明的。
  1. Caretaker 的复杂性: Caretaker 需要管理 Memento 的生命周期和存储(例如,何时保存、何时丢弃、如何组织 Memento 列表/栈),这可能会增加 Caretaker 的复杂性。
  1. 状态创建/恢复成本: 创建 Memento 或从 Memento 恢复状态的操作本身可能比较耗时,取决于状态的复杂程度。

使用场景

  1. 需要保存一个对象在某一个时刻的状态,以便后续能恢复到这个状态。
  1. 需要实现撤销 (Undo) / 重做 (Redo) 功能。
  1. 需要实现事务 (Transaction) 的回滚操作(例如,数据库操作)。
  1. 需要实现检查点 (Checkpoint) 或快照功能,如游戏存档、系统配置的备份与恢复。
  1. Android 中的场景:
      • 实现自定义 View 的撤销/重做: 例如,在一个绘图应用中,用户的每一次绘制操作(添加线条、改变颜色)后,可以将 View 的状态(绘制路径列表、颜色等)保存到 Memento 中,由一个 UndoRedoManager (Caretaker) 管理 Memento 栈。
      • 复杂表单的状态管理: 用户在填写一个多步骤、多字段的复杂表单时,可以阶段性地保存表单状态到 Memento,以便在用户误操作或需要回退时恢复。
      • 配置更改期间保存临时状态: 虽然 Android 有 onSaveInstanceState 和 ViewModel,但在某些特定复杂场景下,你可能需要在 ViewModel 内部使用 Memento 模式来管理更精细的状态快照和恢复逻辑。
      • 游戏开发: 保存游戏进度是 Memento 模式的典型应用。

UML 图

下面是基于一个简单的“文本编辑器 (TextEditor)”示例的备忘录模式 UML 图:
  • TextEditor (Originator): 包含 text 状态。createMemento() 创建一个 EditorMemento 来保存当前 text。restore(memento) 使用传入的 EditorMemento 恢复 text。
  • EditorMemento (Memento): 存储 text 状态 (state)。关键在于其内部状态 state 理论上只应被 TextEditor 访问。(UML 中用 ~ 表示 package-private 或 internal,但这只是示意)。
  • History (Caretaker): 持有一个 TextEditor 引用和一个 EditorMemento 的栈 (historyStack)。save() 方法让 TextEditor 创建 Memento 并压入栈。undo() 方法弹出栈顶 Memento 并让 TextEditor 恢复状态。
  • Client: 创建 TextEditor 和 History,模拟用户操作(修改文本)、保存状态和撤销操作。

代码示例

在这个例子中:
  • TextEditor 是 Originator,负责管理文本内容,并能创建 (createMemento) 和恢复 (restore) 状态。
  • EditorMemento 是 Memento,它存储了某个时刻的 text 状态。通过 internal 关键字限制了状态的访问,主要供 TextEditor 使用。
  • History 是 Caretaker,使用一个栈 (historyStack) 来保存 EditorMemento 对象,实现了 save 和 undo 操作。它不关心 Memento 内部存的是什么。
  • 客户端代码演示了编辑文本、保存状态以及执行撤销操作的过程。

总结

  • 备忘录模式提供了一种在不破坏对象封装性的前提下,实现对象状态快照和恢复的机制。
  • 它将状态存储 (Memento)、状态所有者 (Originator) 和状态保管者 (Caretaker) 三者的职责清晰地分开。
  • 核心优势在于保护了 Originator 的内部细节,同时提供了实现撤销/重做、存档/读档等功能的标准方法。需要注意内存消耗和 Memento 的访问控制实现。
💡
记住它的核心:想反悔?找备忘录!发起人存,管理人收,恢复时发起人自己懂。
 
设计模式——结构型-享元模式设计模式——行为型-迭代器模式