1. 概述
本文将深入讲解 Memento(备忘录)设计模式的核心思想与实际应用。
先从理论入手,再通过一个贴近实际的文本编辑器示例,带你掌握如何在 Java 中优雅地实现“撤销”功能。踩过坑的都知道,状态回滚看似简单,一旦设计不好就会破坏封装、污染代码,而 Memento 模式正是为此而生。
2. 什么是 Memento 设计模式?
Memento 是 GoF(Gang of Four)提出的一种行为型设计模式,核心用途是:✅ 实现操作的撤销(Undo)机制。
它的实现思路很直观:在某个时间点保存对象的状态,在需要时恢复。整个过程涉及三个关键角色:
- Originator(原发器):需要保存和恢复状态的对象。
- Memento(备忘录):用于存储 Originator 的状态快照。
- Caretaker(管理者):负责保存 Memento,并在适当时机触发恢复。
⚠️ 关键原则:Memento 对外(尤其是 Caretaker)应尽可能隐藏内部细节,避免破坏 Originator 的封装性。只有 Originator 本身才能完全读取并恢复状态。
下图展示了三者之间的关系:
可以看到:
- Originator 可以生成(save)和消费(restore)Memento。
- Caretaker 只负责持有 Memento,不关心其内容。
- Originator 的内部状态对外不可见。
📌 补充说明:
- Memento 存储的状态字段数量不限,可以是部分状态,只要足够恢复即可。
- 不必保存 Originator 的全部字段,按需裁剪可节省内存。
3. 何时使用 Memento 模式?
✅ 推荐使用场景:
- 需要实现撤销、重做(Undo/Redo)功能
- 对象状态需要快照备份(如事务回滚、游戏存档)
- 希望解耦状态保存逻辑与业务逻辑
❌ 需谨慎使用的情况:
- Originator 状态非常庞大(如富文本编辑器整篇文档)
- 频繁保存会导致内存暴涨或性能下降
⚠️ 踩坑提示:如果状态太大,考虑只保存差异(diff)或采用懒加载策略,避免“备忘录爆炸”。
4. Memento 模式实战
我们以一个简易文本编辑器为例,逐步实现保存和撤销功能。
4.1 基础结构
先定义两个核心类:TextEditor
(编辑器)和 TextWindow
(文本窗口)。
public class TextEditor {
private TextWindow textWindow;
public TextEditor(TextWindow textWindow) {
this.textWindow = textWindow;
}
}
public class TextWindow {
private StringBuilder currentText;
public TextWindow() {
this.currentText = new StringBuilder();
}
public void addText(String text) {
currentText.append(text);
}
}
此时编辑器只能添加文本,无法撤销。
4.2 定义 Memento
我们要保存文本状态,因此创建 TextWindowState
类作为 Memento:
public class TextWindowState {
private final String text;
public TextWindowState(String text) {
this.text = text;
}
public String getText() {
return text;
}
}
✅ 注意点:
- 使用
String
而非StringBuilder
,防止外部篡改(不可变性)。 - 字段设为
final
,增强安全性。
4.3 实现 Originator
让 TextWindow
成为 Originator,添加 save()
和 restore()
方法:
public class TextWindow {
private StringBuilder currentText;
public TextWindow() {
this.currentText = new StringBuilder();
}
public void addText(String text) {
currentText.append(text);
}
// 生成 Memento
public TextWindowState save() {
return new TextWindowState(currentText.toString());
}
// 恢复状态
public void restore(TextWindowState save) {
this.currentText = new StringBuilder(save.getText());
}
}
Originator 掌控状态的序列化与反序列化,完全自主。
4.4 实现 Caretaker
TextEditor
作为管理者,负责调用保存和恢复:
public class TextEditor {
private final TextWindow textWindow;
private TextWindowState savedTextWindow; // 持有 Memento
public TextEditor(TextWindow textWindow) {
this.textWindow = textWindow;
}
public void write(String text) {
textWindow.addText(text);
}
// 保存当前状态
public void hitSave() {
savedTextWindow = textWindow.save();
}
// 撤销到上次保存点
public void hitUndo() {
if (savedTextWindow != null) {
textWindow.restore(savedTextWindow);
}
}
// 辅助方法:获取当前文本
public String print() {
return textWindow.getCurrentText();
}
}
📌 补充:为方便测试,给 TextWindow
添加 getCurrentText()
方法:
public String getCurrentText() {
return currentText.toString();
}
4.5 测试验证
来一段简单粗暴的测试,验证撤销功能是否生效:
TextEditor textEditor = new TextEditor(new TextWindow());
textEditor.write("The Memento Design Pattern\n");
textEditor.write("How to implement it in Java?\n");
textEditor.hitSave(); // 保存当前状态
textEditor.write("Buy milk and eggs before coming home\n");
textEditor.hitUndo(); // 撤销
assertThat(textEditor.print()).isEqualTo("The Memento Design Pattern\nHow to implement it in Java?\n");
✅ 输出结果符合预期:最后一行“买牛奶”已被撤销。
说明 Memento 成功保存了中间状态,并在 hitUndo()
时准确恢复。
5. 总结
Memento 模式通过分离状态保存与管理逻辑,实现了干净、安全的撤销机制。核心优势在于:
- ✅ 封装性好:外部无法窥探或修改 Memento 内容
- ✅ 职责清晰:Originator 负责状态,Caretaker 负责流程
- ✅ 易扩展:支持多级撤销(只需用栈管理多个 Memento)
在实际项目中,可用于富文本编辑、配置回滚、游戏存档等场景。但也要警惕状态过大带来的内存问题。
完整代码示例已托管至 GitHub:https://github.com/eugenp/tutorials/tree/master/patterns-modules/design-patterns-behavioral-2