1 动机
备忘录模式(Memento Pattern),又称为快照模式(Snapshot Pattern),为对象提供了恢复到先前状态的能力(通过回滚(rollback))。
一个典型的例子是文本编辑器(text editor)可以在任何时刻保存,并且使用撤销(undo)来撤回到先前的保存状态。
2 定义
**备忘录模式(Memento Pattern)**是在不破坏封装的前提下,获取一个对象的内部状态,并将这个状态保存在这个对象之外,以实现在未来将对象恢复到先前状态的一种模式。
常见的软件系统往往不止存储一个状态,而是需要存储多个状态。这些状态常常是一个对象历史发展的不同阶段的快照,存储这些快照的备忘录对象叫做对象的历史;某一个快照所处的位置叫做检查点(Check Point)。
3 构成
- 发起者角色(Originator):它的状态会被保存起来。而保存机制通过使用一个内部类(inner class)来实现,这个内部类就是备忘录角色(Memento),备忘录角色被定义在发起者内部。
- 负责者角色(Caretaker):帮助存储和恢复发起者的状态。Caretaker会在内部维护一个状态池(用于保存每一次发起者的状态)。
- 备忘录角色(Memento):每一次保存,发起者的状态都会被保存在一个备忘录角色的实例中。它是一个内部类,因此它不能被(除发起者外的)任何类访问。
交互
- 每一次调用负责者以保存
Originator
的状态,负责者都会将FileWriterUtil
的当前状态存储于一个备忘录角色对象中。若保存了5次,则会有5个备忘录角色实例。 - 每一次调用负责者以撤回,都会从在状态池中获取先前存储的
Memento
对象(表示当时的那个状态),并改写Originator
对象。
UML图
发起者角色(Originator)
class Originator {
private String state;
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public Originator(String state) {
this.state = state;
}
/**
* 工厂方法,返还一个新的备忘录对象
*/
public Memento createMemento() {
return new Memento(this.state);
}
public void restoreMemento(Memento memento) {
Memento m = (Memento) memento;
this.setState(m.getSavedState());
}
}
负责者角色(Caretaker)
备忘录角色(Memento)
4 示例
我们实现一个可以撤回的文本编辑器以作为例子。每次保存时,都会将文本内容写入文件中。
注意,为了尽可能简化实现而突出问题实质,将文本写入文件的逻辑在以下实现中省略了。
UML图
实现思路
FileWriterCaretaker
类对应Caretaker
角色,为调用者提供接口,用于保存或撤回(恢复先前状态)。FileWriterUtil
表示当前文本Memento
表示每一次保存时,文本的状态
Originator 和内部类Memento
class FileWriterUtil {
private String fileName;
private StringBuilder content;
public FileWriterUtil(String file) {
this.fileName = file;
this.content = new StringBuilder();
}
@Override
public String toString() {
return this.content.toString();
}
public void write(String str) {
content.append(str);
}
public Memento save() {
return new Memento(this.fileName, this.content);
}
public void undoToLastSave(Object obj) {
Memento memento = (Memento) obj;
this.fileName = memento.fileName;
this.content = memento.content;
}
private class Memento {
private String fileName;
private StringBuilder content;
public Memento(String file, StringBuilder content) {
this.fileName = file;
//notice the deep copy so that Memento and FileWriterUtil content variables don't refer to same object
this.content = new StringBuilder(content);
}
}
}
Caretaker
class FileWriterCaretaker {
private Object obj;
public void save(FileWriterUtil fileWriter) {
this.obj = fileWriter.save();
}
public void undo(FileWriterUtil fileWriter) {
fileWriter.undoToLastSave(obj);
}
}
调用者调用代码
public class Client {
public static void main(String[] args) {
FileWriterCaretaker caretaker = new FileWriterCaretaker();
FileWriterUtil fileWriter = new FileWriterUtil("data.txt");
fileWriter.write("First Set of Data\n");
System.out.println(fileWriter + "\n");
// lets save the file
caretaker.save(fileWriter);
//now write something else
fileWriter.write("Second Set of Data\n");
//checking file contents
System.out.println(fileWriter + "\n");
//lets undo to last save
caretaker.undo(fileWriter);
//checking file content again
System.out.println(fileWriter + "\n");
}
}
输出
/* output:
First Set of Data
First Set of Data
Second Set of Data
First Set of Data
*/
分析
FileWriterUtil
对象表示当前的文本- 每一次调用
FileWriterCaretaker
以保存FileWriterUtil
的状态,FileWriterCaretaker
都会将FileWriterUtil
的当前状态存储于一个Memento
对象中。若保存了5次,则会有5个Memento
实例 - 每一次调用
Caretaker
以撤回,都会从在状态池中获取先前存储的Memento
对象(表示当时的那个状态),并改写FileWriterUtil
对象 - 在上面例子的
Caretaker
中,private Object obj;
的声明决定了只会恢复到上一次的状态(撤销一次)。若希望允许多次撤销),则可以修改为private List<Object> obj;
以保存多次状态 - 在上面的例子中,我们采用深拷贝(deep copy)的方式来保存文本的状态,以避免数据完整性问题
- 在上面的例子中,我们简单粗暴地采取了全量存储状态的方式,若当前文本对象较大,这样的做法是极为消耗内存的。取而代之,我们可以采取增量存储的方式来作为实现的优化
模式的优缺点
优点
- 给用户提供了一种可以恢复状态的机制。可以是用户能够比较方便地回到某个历史的状态。
- 实现了信息的封装。使得用户不需要关心状态的保存细节。
缺点
消耗资源。如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。
模式适用场景
- 需要保存一个对象在某一个时刻的状态或部分状态。
- 如果用一个接口来让其他对象得到这些状态,将会暴露对象的实现细节并破坏对象的封装性,一个对象不希望外界直接访问其内部状态,通过负责人可以间接访问其内部状态。
Reference
- 《Java与模式》
- Design Patterns: Elements of Reusable Object-Oriented Software
- JournalDev - Memento Design Pattern in Java - https://www.journaldev.com/1734/memento-design-pattern-java
- https://github.com/iluwatar/java-design-patterns/tree/master/memento
- 设计模式读书笔记——备忘录模式 - https://www.cnblogs.com/chenssy/p/3341526.html