备忘录模式是一种行为设计模式,在不破坏封装性的前提下,允许在不暴露对象实现细节的情况下保存和恢复对象之前的状态。
Memento is a behavior design pattern. Without compromising encapsulation,
it can reserve and restore of the previous state of an object, not exposing
its implementation details.
结构设计
一个备忘录(memento)是一个对象,它存储另一个对象在某个瞬间的内部状态,而后者称为备忘录的原发器(originator)。当需要设置原发器的检查点时,取消操作机制会向原发器请求一个备忘录。
原发器用描述当前状态的信息初始化备忘录。只有原发器可以向备忘录中存取信息,备忘录对其他的对象"不可见"。
备忘录模式包含如下角色:
Originator,原发器,可以生成自身状态的快照,也可以在需要时通过快照恢复自身状态。
Memento,备忘录,是原发器状态快照的值对象(value object)。通常做法是将备忘录设为不可变的,并通过构造函数一次性传递数据。
Caretaker,负责人,仅知道“何时”和“为何”捕捉原发器的状态,以及何时恢复状态。负责人通过保存备忘录栈来记录原发器的历史状态。 当原发器需要回溯历史状态时,
负责人将从栈中获取最顶部的备忘录, 并将其传递给原发器的恢复(restoration)方法。
备忘录模式类图表示如下:
伪代码实现
接下来将使用代码介绍下备忘录模式的实现。
// 1、原发器,支持读写自身状态,支持生成自身状态的快照,支持通过快照恢复自身状态
public class Originator {
private String name;
private String describe;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void setDescribe(String describe) {
this.describe = describe;
}
public String getDescribe() {
return this.describe;
}
public Memento save() {
return new Memento(this, name, describe);
}
public void restore(Memento memento) {
setName(memento.getName());
setDescribe(memento.getDescribe());
}
}
//2、备忘录,是原发器状态快照的值对象
public class Memento {
private String name;
private String describe;
private Originator originator;
public Memento(Originator originator, String name, String describe) {
this.originator = originator;
this.name = name;
this.describe = describe;
}
public String getName() {
return this.name;
}
public String getDescribe() {
return this.describe;
}
}
// 3、负责人,通过保存备忘录栈来记录原发器的历史状态。当原发器需要回溯历史状态时,负责人将从栈中获取最顶部的备忘录,并将其传递给原发器的恢复(restoration)方法
public class Caretaker {
private Originator originator;
private LinkedList<Memento> history;
public Caretaker(Originator originator) {
this.originator = originator;
history = new LinkedList<>();
}
public void snapshot() {
history.push(originator.save());
}
public void undo() {
Memento lastMemento = history.pop();
originator.restore(lastMemento);
}
}
// 4、客户端
public class MementoClient {
public void test() {
// (1) 创建原生器实例并设置状态
Originator originator = new Originator();
originator.setName("1");
originator.setDescribe("one");
// (2) 创建负责人实例
Caretaker caretaker = new Caretaker(originator);
// (3) 创建快照
caretaker.snapshot();
System.out.println("name is " + originator.getName() + " , " + "describe is " + originator.getDescribe());
originator.setName("2");
originator.setDescribe("two");
caretaker.snapshot();
System.out.println("name is " + originator.getName() + " , " + "describe is " + originator.getDescribe());
// (4) 恢复上一个状态
caretaker.undo();
System.out.println("name is " + originator.getName() + " , " + "describe is " + originator.getDescribe());
caretaker.undo();
System.out.println("name is " + originator.getName() + " , " + "describe is " + originator.getDescribe());
}
}
适用场景
在以下情况下可以考虑使用备忘录模式:
(1) 当需要创建对象状态快照来恢复其之前的状态时,可以考虑使用备忘录模式。 备忘录模式允许复制对象中的全部状态(包括私有成员变量),并将其独立于对象进行保存。
尽管大部分人因为 “撤销” 这个用例才记得该模式,但其实它在处理事务(比如需要在出现错误时回滚一个操作)的过程中也必不可少。
(2) 当直接访问对象的成员变量、获取器或设置器将导致封装被突破时,可以考虑使用备忘录模式。备忘录让对象自行负责创建其状态的快照。任何其他对象都不能读取快照,这有效地保障了数据的安全性。
优缺点
备忘录模式有以下优点:
(1) 可以在不破坏对象封装情况的前提下创建对象状态快照。
(2) 可以通过让负责人维护原发器状态历史记录来简化原发器代码。
(3) 给用户提供了一种可恢复状态的机制,能够比较方便地回滚到某个历史状态。
但是该模式也存在以下缺点:
(1) 如果客户端过于频繁地创建备忘录,程序将消耗大量内存。
(2) 负责人必须完整跟踪原发器的生命周期,这样才能销毁弃用的备忘录。
参考
《设计模式 可复用面向对象软件的基础》 Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides 著, 李英军, 马晓星等译
https://refactoringguru.cn/design-patterns/memento 备忘录模式
https://www.runoob.com/design-pattern/memento-pattern.html 备忘录模式
https://www.cnblogs.com/adamjwh/p/11018268.html 简说设计模式——备忘录模式