携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第 2 天,点击查看活动详情
Intent 意图
在允许不暴露对象实现细节的前提下保存和恢复对象之前的状态;备忘录让对象自行负责创建其状态的快照。 任何其他对象都不能读取快照, 这有效地保障了数据的安全性。
Motivation 动机
大部分类为了维护自己的状态或者一些重要数据对象,会将数据改为私有成员保存在类的本身。这就规定了,这些类成员的访问需要有一定的权限才可以访问。在需要对某些类或者对象生成快照,以便保存或者回滚某些状态时,我们一般将这些状态或者数据成员对象额外保存一份,当需要回滚的时候,才将其重新赋值。但这又会引入一个新的问题,这些状态都是公开的,那么就很容易暴露了原本类中不愿意让人看到的实现细节。
Applicability 适用范围
- 当需要创建某个时刻下对象快照的时候,可以使用备忘录模式
Structure 结构
| 由于没有找到更加合适的结构图,所以使用了 guru 的示例图哈
- 支持嵌套类
- 不支持嵌套类的实现,基于中间接口类的模式
- 更加严格的实现方式
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:
(包括本文参考、或本文的截图及示例出处)