备忘录模式是什么?
备忘录模式(Memento Design)是一种行为型的设计模式。根据 GoF 对备忘录模式的定义:
Without violating encapsulation, capture and externalize an object’s internal state so that the object can be restored to this state later.
这句话的意思是说,这个模式是在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存此状态,这样以后就可以将这个对象恢复到先前保存的状态。
我们可以举一个生活中应用备忘录模式的例子:相信大家都玩过超级马里奥这款游戏,游戏中马里奥要闯过诸多的关卡直到最后一关救出公主才能赢得最后的胜利。闯关过程中,如果马里奥触碰到怪物或者掉入悬崖,那么马里奥就会失去一次机会,需要再次闯关,但是马里奥仍是从当前关卡继续冒险的,而不是从第一关开始冒险。这是因为每当马里奥通过一个关卡后,游戏就会存档,所以,当马里奥在某个关卡失败时,游戏可以加载之前保存的档案,继续从当前的关卡冒险。
UML 类图
用 UML 类图来描述备忘录模式的结构,在模式中各个角色之间的关系:
根据上图,总结了模式中各个角色的职责以及它们之间的关系:
- 原发器可以生成自身状态的快照,也可以在适当的时机通过快照恢复自身的状态。
- 备忘录是原发器的状态快照的值对象,通常来说,备忘录应该是不可变的(immutable),仅通过构造方法一次性传递数据。
- 负责人不仅知道“何时”以及“为何”捕捉原发器的对象,还知道“如何”恢复它的状态。通常情况下,负责人是通过保存备忘录栈来记录原发器的历史状态。当原发器需要回溯历史状态时,负责人只需从栈中获取最顶部的备忘录,并将其传递给原发器的还原方法。
案例
让我们通过一个案例来帮助我们进一步理解备忘录模式。假设我们现在要开发一个文本编辑器,编辑器需要支持撤消的功能。
首先,我们定义一个 TextEditor 类,表示编辑器。TextEditor 类包含一个 TextWindow 类型的属性,它是编辑器的文本区,用来接受并保存用户输入的文本内容。
public class TextEditor {
private TextWindow textWindow;
public TextEditor(TextWindow textWindow) {
this.textWindow = textWindow;
}
}
public class TextWindow {
private StringBuilder currentText = new StringBuilder();
public String getCurrentText() {
return currentText.toString();
}
public void addText(String text) {
currentText.append(text);
}
}
为了支持撤消的功能,我们定义一个 TextWindowState 类来保存 TextWindow 对象的状态。
@EqualsAndHashCode
public class TextWindowState {
private String text;
// getter
public TextWindowState(String text) {
this.text = text;
}
// equals and hasCode
}
现在,我们可以为 TextWindow 类提供保存状态和恢复状态的方法。
public class TextWindow {
// ...
public TextWindowState save() {
return new TextWindowState(currentText.toString());
}
public void restore(TextWindowState save) {
currentText = new StringBuilder(save.getText());
}
}
有了 TextWindow 的保存、恢复操作,就能完成编辑器的保存、撤消功能。
public class TextEditor {
// ...
private Stack<TextWindowState> history = new Stack<>();
public void save() {
TextWindowState newState = textWindow.save();
if (history.isEmpty() || !history.peek().equals(newState)) {
history.push(newState);
}
}
public void undo() {
if (!history.isEmpty()) {
textWindow.restore(history.pop());
}
}
}
再为编辑器完善一下打字和显示文本的功能,一个简化版的编辑器就完成了。
public class TextEditor {
// ...
public void typewrite(String text) {
textWindow.addText(text);
}
public void print(OutputStream outputStream) throws IOException {
outputStream.write(textWindow.getCurrentText().getBytes());
}
}
最后,我们通过一个用例来检验一下编辑器的是否能正常使用。
public class MementoMain {
public static void main(String[] args) throws IOException {
TextEditor textEditor = new TextEditor(new TextWindow());
textEditor.typewrite("The Memento Design Pattern\n");
textEditor.save();
textEditor.typewrite("How to implement it in Java?\n");
textEditor.undo();
textEditor.print(System.out);
}
}
案例源码
可在 GitHub 上查看上述案例的完整代码。
参考资料
本文参考的资料如下: