备忘录模式(Memento Pattern)
类型:行为型(GoF 23 种之一)
核心意图:在不破坏封装性的前提下,捕获并外部化对象的内部状态,以便之后可将该对象恢复到原先保存的状态。
关键词:快照、撤销(Undo)、回滚、存档。
一、角色与结构
Originator(原发器)
真正拥有内部状态的类;可创建备忘录、也可根据备忘录恢复自身状态。Memento(备忘录)
存储 Originator 的某一时刻内部状态;对其它对象只暴露窄接口(只读),防止外部随意篡改。Caretaker(看管者)
负责保存备忘录列表/栈,但不能操作备忘录内容;典型实现为“撤销管理器”。
类图(简化)
Originator ──createMemento()──> Memento
▲ ▲
│ │
│ │
Caretaker ──holds──> List<Memento>
二、Java 代码示例(文本编辑器撤销/重做)
- 备忘录(Memento)
public final class EditorMemento {
private final String content;
private final int cursor;
EditorMemento(String content, int cursor) {
this.content = content;
this.cursor = cursor;
}
/* 仅包可见,防止外部直接访问 */
String getContent() { return content; }
int getCursor() { return cursor; }
}
- 原发器(Originator)
public class Editor {
private String content = "";
private int cursor = 0;
public void write(String text) {
content += text;
cursor = content.length();
}
public void delete(int length) {
if (length > content.length()) length = content.length();
content = content.substring(0, content.length() - length);
cursor = content.length();
}
/* 创建快照 */
public EditorMemento save() {
return new EditorMemento(content, cursor);
}
/* 恢复状态 */
public void restore(EditorMemento m) {
this.content = m.getContent();
this.cursor = m.getCursor();
}
@Override public String toString() {
return "Content: " + content + ", Cursor: " + cursor;
}
}
- 看管者(Caretaker)
public class History {
private final Deque<EditorMemento> stack = new ArrayDeque<>();
public void push(EditorMemento m) { stack.push(m); }
public EditorMemento pop() { return stack.isEmpty() ? null : stack.pop(); }
}
- 客户端
public class Client {
public static void main(String[] args) {
Editor editor = new Editor();
History history = new History();
editor.write("Hello");
history.push(editor.save()); // 第1次快照
editor.write(" World");
history.push(editor.save()); // 第2次快照
editor.delete(5);
System.out.println(editor); // Content: Hello, Cursor: 5
editor.restore(history.pop()); // 撤销
System.out.println(editor); // Content: Hello World, Cursor: 11
editor.restore(history.pop()); // 再撤销
System.out.println(editor); // Content: Hello, Cursor: 5
}
}
三、黑箱 vs 白箱实现
- 白箱:把 Memento 设为 public,Caretaker 可直接访问内部字段(破坏封装,不推荐)。
- 黑箱:Memento 接口仅暴露只读方法,Originator 用私有内部类实现真正数据(示例即黑箱)。
四、与命令模式的协作
命令模式负责“做什么”,备忘录模式负责“恢复到什么状态”。
典型做法:
- 每条命令执行前,让 Caretaker 保存一次 Originator 的快照;
- 撤销时,命令对象从 Caretaker 取回对应备忘录并调用 Originator.restore()。
五、优缺点
优点
- 严格封装:外部无法触碰 Originator 内部细节。
- 简化 Originator:状态保存/恢复逻辑被剥离到 Memento。
- 支持多级撤销、重做、时间旅行调试。
缺点
- 资源消耗:大量快照会占用内存;可结合“增量存储”或“最大撤销深度”优化。
- 维护同步:Originator 字段变化时,Memento 也要同步调整。
六、JDK 中的备忘录影子
java.util.Date
的clone()
机制(浅拷贝快照)。javax.swing.undo.UndoManager
与StateEditable
接口。- 游戏存档、数据库事务回滚、虚拟机快照(KVM/QEMU)本质都是备忘录思想。
七、一句话总结
备忘录模式把“状态”变成可存储、可回滚的独立对象,让“撤销/重做”功能在面向对象世界里优雅落地。