摘要生成中...
AI 摘要
Hunyuan-lite

image.png

备忘录模式/快照模式 Memento Design Pattern

Captures and externalizes an object’s internal state so that it can be restored later, all without violating encapsulation.——GoF
在不违背封装原则的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便之后恢复对象为先前的状态。

image.png

备忘录模式可以实现的功能:

  • Undo(撤销)
  • Redo(重做)
  • History(历史记录)
  • Snapshot(快照)

备忘录模式的实现很灵活,也没有很固定的实现方式,在不同的业务需求、不同编程语言下,代码实现可能都不大一样。

image.png

注意:

  • OriginatorMemento 不是父子关系。
  • Caretaker 无法 newMemento 实例,只能通知 Originatornew
  • 为了实现访问权限控制,注意 OriginatorMemento 的包的关系。
1
2
3
4
5
6
7
8
9
10
class Originator{
public Memento createMemento(){
Memento memento=new Memento(/*...*/);
//do something
return memento;
}
public void restoreMemento(Memento memento){
//do something
}
}

登场角色:

  • Originator(原发器):Originator 角色会在保存自己的最新状态时生成 Memento 角色。当把以前保存的 Memento 角色传递给 Originator 角色时,它会将自己恢复至生成该 Memento 角色时的状态。
  • Memento(备忘录):
    • Originator 角色和 Memento 角色之间有着非常紧密的联系。Memento 角色会将 Originator 角色的内部信息整合在一起。
    • Memento 角色中虽然保存了 Originator 角色的信息,但它不会向外部公开这些信息。Memento 角色有以下两种接口(API)以防止对象的封装性被破坏:
      • 宽接口(API)——Wide interface。Memento 角色提供的「宽接口(API)」是指所有用于获取恢复对象状态信息的方法的集合。由于宽接口(API)会暴露所有 Memento 角色的内部信息,因此能够使用宽接口(API)的只有 Originator 角色。
      • 窄接口(API)——Narrow interface。Memento 角色为外部的 Caretaker 角色提供了「窄接口(API)」。可以通过窄接口(API)获取的 Memento 角色的内部信息非常有限,因此可以有效地防止信息泄露。
  • Caretaker(负责人):当 Caretaker 角色想要保存当前的 Originator 角色的状态时,会通知 Originator 角色。Originator 角色在接收到通知后会生成 Memento 角色的实例并将其返回给 Caretaker 角色。
    • 负责人仅知道「何时」和「为何」捕捉原发器的状态,以及何时恢复状态。
    • 由于以后可能会用 Memento 实例来将 Originator 恢复至原来的状态,因此 Caretaker 角色会一直保存 Memento 实例。不过,Caretaker 角色只能使用 Memento 角色的窄接口(API),也就是说它无法访问 Memento 角色内部的所有信息。它只是将 Originator 角色生成的 Memento 角色当作一个黑盒子保存起来。
    • 虽然 Originator 角色和 Memento 角色之间是强关联关系,但 Caretaker 角色和 Memento 角色之间是弱关联关系。Memento 角色对 Caretaker 角色隐藏了自身的内部信息。
适用于不支持嵌套类的编程语言的实现方法

比如 PHP 语言。
image.png

  1. 在没有嵌套类的情况下, 你可以规定负责人仅可通过明确声明的中间接口与备忘录互动, 该接口仅声明与备忘录元数据相关的方法, 限制其对备忘录成员变量的直接访问权限。
  2. 另一方面, 原发器可以直接与备忘录对象进行交互, 访问备忘录类中声明的成员变量和方法。 这种方式的缺点在于你需要将备忘录的所有成员变量声明为公有。
封装更加严格的实现

image.png

  1. 这种实现方式允许存在多种不同类型的原发器和备忘录。 每种原发器都和其相应的备忘录类进行交互。 原发器和备忘录都不会将其状态暴露给其他类。
  2. 负责人此时被明确禁止修改存储在备忘录中的状态。 但负责人类将独立于原发器, 因为此时恢复方法被定义在了备忘录类中。
  3. 每个备忘录将与创建了自身的原发器连接。 原发器会将自己及状态传递给备忘录的构造函数。 由于这些类之间的紧密联系, 只要原发器定义了合适的设置器 (setter), 备忘录就能恢复其状态。

拓展思路:

  • 两种接口(API)的可见性。控制访问权限。
  • 可以实现保存多个 Memento,这样就可以保存各个时间点的状态。
  • 注意 Memento 的有效期。
  • Caretaker 决定何时拍照,何时撤销以及保存 Memento 角色。Originator 角色的职责是生成 Memento 角色和使用接收到的 Memento 角色来恢复自己的状态。这样的职责分担,当遇到以下需求时,Originator 不用改变:
    • 可以多次撤销
    • 不仅可以多次撤销,还可以将现在的状态保存在文件中
优点 缺点
可以在不破坏对象封装情况的前提下创建对象状态快照。 如果客户端过于频繁地创建备忘录,程序将消耗大量内存。
可以通过让负责人维护原发器状态历史记录来简化原发器代码。 负责人必须完整跟踪原发器的生命周期,这样才能销毁弃用的备忘录。
绝大部分动态编程语言(例如 PHP、Python 和 JavaScript)不能确保备忘录中的状态不被修改。

备忘录模式与「备份」的概念区别:

  • 这两者的应用场景很类似,都应用在防丢失、恢复、撤销等场景中。
  • 备忘录模式更侧重于代码的设计和实现;备份更侧重架构设计或产品设计。

使用「低频率全量备份」和「高频率增量备份」方式可以优化内存和时间消耗。

image.png

相关的设计模式:

  • 站内文章命令模式:在使用命令模式处理命令时,可以使用备忘录模式实现撤销功能。
  • 站内文章原型模式:在备忘录模式中,为了能够实现快照和撤销功能,保存了对象当前的状态。保存的信息只是在恢复状态时所需要的那部分信息。而在原型模式中,会生成一个与当前实例完全相同的另外一个实例。这两个实例的内容完全一样。
  • 站内文章状态模式:在备忘录模式中,是用「实例」表示状态。而在状态模式中,则是用「类」表示状态。
  • 站内文章迭代器模式:可以同时使用备忘录和迭代器模式来获取当前迭代器的状态,并且在需要的时候进行回滚。

本文参考