设计模式 | 备忘录模式

203 阅读5分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第 2 天,点击查看活动详情

Intent 意图

在允许不暴露对象实现细节的前提下保存和恢复对象之前的状态;备忘录让对象自行负责创建其状态的快照。 任何其他对象都不能读取快照, 这有效地保障了数据的安全性。

Motivation 动机

大部分类为了维护自己的状态或者一些重要数据对象,会将数据改为私有成员保存在类的本身。这就规定了,这些类成员的访问需要有一定的权限才可以访问。在需要对某些类或者对象生成快照,以便保存或者回滚某些状态时,我们一般将这些状态或者数据成员对象额外保存一份,当需要回滚的时候,才将其重新赋值。但这又会引入一个新的问题,这些状态都是公开的,那么就很容易暴露了原本类中不愿意让人看到的实现细节。

Applicability 适用范围

  • 当需要创建某个时刻下对象快照的时候,可以使用备忘录模式

Structure 结构

| 由于没有找到更加合适的结构图,所以使用了 guru 的示例图哈

  • 支持嵌套类

image.png

  • 不支持嵌套类的实现,基于中间接口类的模式

image.png

  • 更加严格的实现方式

image.png

Participate 结构成员

结构中各个类、对象所扮演的角色

  • Originator | 原始类

    • 被记录的对象类
    • save 当执行时,相当于调用了一次 take snapshot, 保存这个时刻的 originator 类中所有的状态
  • Memento or snapshot | 备忘录

    • 定义里面可实现的接口
    • 备忘录类具有不可变性,需要将备忘录声明为不可变,备忘录只能通过构造函数一次性接收数据。 该类中不能包含设置器。外部操作不能改变备忘录里面的值。
  • Concrete Memento | 具体的备忘录类

    • 持有 originator 的某一个时刻下的所有状态,逐一声明对应每个原发器成员变量的备忘录成员变量。
    • restore or getState 让 originator 类能够访问快照里面的成员状态,或者直接给 originator 类设置快照中的成员状态类
  • Caretaker | 负责人

    • 持有 originator 的引用,在严格模式中,他不持有这个 originator 类的引用,他独立于 originator 类,只作为一个触发器,作为什么时候可以触发恢复方法
    • 拥有一个 history, 里面保存的是所有时刻的 snapshot ,当执行 undo 的时候,给出对应的 snapshot 并执行 snapshot 中的 restore 方法,将 originator 类还原到该时刻

Cooperation 协作

  • 原始类只和备忘录关联,只有备忘录可以设置或者访问原始类的所有对象方法。负责人与原发器之间的连接可以移动到备忘录类中。 每个备忘录都必须与创建自己的原发器相连接。 恢复方法也可以移动到备忘录类中, 但只有当备忘录类嵌套在原发器中, 或者原发器类提供了足够多的设置器并可对其状态进行重写时, 这种方式才能实现。

Consequence 后果

  • Good

    • 你可以在不破坏对象封装情况的前提下创建对象状态快照。
    • 你可以通过让负责人维护原发器状态历史记录来简化原发器代码。
  • Bad

    • 如果客户端过于频繁地创建备忘录, 程序将消耗大量内存。
    • 负责人必须完整跟踪原发器的生命周期, 这样才能销毁弃用的备忘录。
    • 绝大部分动态编程语言 (例如 PHP、 Python 和 JavaScript) 不能确保备忘录中的状态不被修改。

Known uses 已知应用

  • macos 的系统快照类似
  • Words or WPS 类似的保存,但是更加常规的方式会结合命令者模式一块实现

Related Pattern 和其他模式直接的关系

命令 or 备忘录

  • 命令:更加 focus 一个个的命令对象,一个个指令,在 undo 环节,其实是逆序将每个命令对象的 undo 方法执行一遍,但是如果方法前后状态有依赖或者状态改变 undo 操作可能不符合预期
  • 备忘录:保存某个时刻下,某个对象的状态或者数据,在 undo 环节中,直接将某个时刻下的状态完成还原,而不依赖于单个执行操作。

code

class Originator {
  private state: number; // 不允许外部调用
  constructor(state = 0) {
    this.state = state;
  }
  // doing something that can change the originator's state
  doRandoThing() {
    this.state = Math.random() * 100;
  }
  // key method
  save(): Memento {
    return new ConcreteMemento(this.state);
  }
  // key method
  restore(snapshot: Memento) {
    this.state = snapshot.getSnapshot();
  }
}

interface Memento {
  getName(): string;
  getDate(): string;
  // key methods 这里的返回有点偷懒,使用了 state 的类型,其实应该整一个返回类型好一点
  getSnapshot(): number; 
}

class ConcreteMemento implements Memento {
  private state: number;
  date: string;
  // 这里的 state 同理,由于只是示例,所以只有一个 state,但实际上的 state 可能是多个不同的变量值
  constructor(state: number) {
    this.date = new Date().toISOString().slice(0, 19).replace('T', ' ');
    this.state = state;
  }
  getName(): string {
    return `${this.date} / (${this.state}...)`;
  }
  getDate(): string {
    return this.date;
  }
  // key methods
  getSnapshot() {
    return this.state;
  }
}

class Caretaker {
  originator: Originator;
  history: Memento[] = [];

  constructor(originator: Originator) {
    this.originator = originator;
  }
  // 外部记录 history,去触发 originator save,然后保存镜像 list
  backup() {
    console.log("\nCaretaker: Saving Originator's state...");
    this.history.push(this.originator.save());
  }

  public undo(): void {
    if (!this.history.length) {
      return;
    }
    const memento = this.history.pop();

    console.log(`Caretaker: Restoring state to: ${memento.getName()}`);
    this.originator.restore(memento);
  }

  public showHistory(): void {
    console.log("Caretaker: Here's the list of mementos:");
    for (const memento of this.history) {
      console.log(memento.getName());
    }
  }
}

function main() {
  const originator = new Originator(2);
  const caretaker = new Caretaker(originator);

  caretaker.backup();
  originator.doRandoThing();

  caretaker.backup();
  originator.doRandoThing();

  caretaker.backup();
  originator.doRandoThing();

  caretaker.showHistory();
  caretaker.undo()
}

main()

往期文章 index

【设计模式索引 - 持续更新中】 juejin.cn/post/705899…

Reference:

(包括本文参考、或本文的截图及示例出处)

refactoringguru.cn/design-patt…