5.6-Memento「Token」-备忘录-行为型模式

目的

在不破坏封装性的前提下捕获对象内部状态,在对象外保存该状态以备恢复

示例

一个图形编辑器,它支持图形对象间的连线。用户可用一条直线连接两个矩形,而当用户移动任意一个矩形时,这两个矩形仍能保持连接 考虑需要撤销移动命令的情形:

  • 备忘录存储内部状态:某个对象(称为原发器 originator )保存自身某个瞬间的内部状态为备忘录对象。
  • 对象可以导出内部状态为备忘录:当需要设置原发器的检查点时,取消操作机制会向原发器请求一个备忘录。
  • 也可以依据备忘录恢复内部状态:需要恢复时,编辑器向原发器提供备忘录供其恢复内部状态

譬如计算图形对象连线的 ConstraintSolver 类,可以作为原发器:考虑取消移动的过程:

  • 作为移动操作的一个副作用,编辑器向 ConstraintSolver 请求一个备忘录;ConstraintSolver 创建并返回一个备忘录
  • 此后当用户取消移动操作时,编辑器将上次请求的备忘录送回给 ConstraintSolver
  • ConstraintSolver 改变它的内部结构以精确地将它的等式和变量返回到它们各自先前的状态

结构

  • Memento 备忘录:备忘录存储原发器对象的内部状态。原发器根据需要决定备忘录存储原发器的哪些内部状态
    • 防止原发器以外的其他对象访问备忘录。备忘录实际上有两个接口,
      • 管理者(caretaker)只能看到备忘录的窄接口——它只能将备忘录传递给其他对象。
      • 原发器能够看到一个宽接口,允许它访问返回到先前状态所需的所有数据。
    • 理想的情况是只允许生成本备忘录的那个原发器访问本备忘录的内部状态。
  • Originator 原发器:原发器创建一个备忘录,用以记录当前时刻它的内部状态;并使用备忘录恢复内部状态
  • Caretaker 管理者:负责保存好备忘录,但不能访问或修改备忘录的内容

管理者向原发器请求一个备忘录,保留一段时间后,在需要回退状态时将其送回给原发器

优缺点

  • 保护封装性
  • 简化了原发器:Originator 无需考虑如何保存客户请求过的各个内部状态版本,版本管理由 caretaker 完成。
  • 复制状态的开销:若生成备忘录时必须拷贝并存储大量的信息,或者客户非常频繁地创建备忘录和恢复原发器状态,可能会导致非常大的开销
    • 可能需要使用增量修改
  • 维护状态的开销:管理者负责删除它所维护的备忘录。然而,管理者不知道备忘录中有多少个状态。因此当存储备忘录时,一个本来很小的管理者可能会产生大量的存储开销
  • 宽窄接口:在一些语言中可能难以保证只有原发器可访问备忘录的状态。

实现

宽窄接口

备忘录有两个接口:一个为原发器所使用的宽接口,一个为其他对象所使用的窄接口。

C++中,可将 Originator 作为 Memento 的一个友元,并使 Memento 宽接口为私有的,窄接口为公共的

增量改变

如果备忘录的创建及其返回(给它们的原发器)的顺序是可预测的,备忘录可以仅存储原发器内部状态的增量改变。

例如,一个包含可撤销的命令的历史列表可使用备忘录:

  • 存在一个历史列表,定义了一个特定的顺序,按照这个顺序命令可以被撤销和重做
  • 备忘录可以只存储一个命令所产生的增量改变而不是它所影响的每一个对象的完整状态

相关模式

Command(5.2):命令可使用备忘录来为可撤销的操作维护状态。 Iterator(5.4):如前所述,备忘录可用于迭代。