Java 设计模式 | 备忘录模式(Memento Pattern)全解析 + 实战
在软件开发中,我们经常需要保存对象的历史状态,并在需要时恢复,例如:
- 文本编辑器的撤销/重做功能
- 游戏的存档/回档机制
- 数据库事务的回滚
如果不加控制地使用对象副本来保存状态,不仅浪费内存,而且增加系统复杂度。如何优雅地管理对象的历史状态?—— 备忘录模式(Memento Pattern)登场!
本篇文章基于《Head First 设计模式》,深入剖析 备忘录模式的核心概念、应用场景、优缺点、在 JDK 和 Spring 框架中的应用,并提供 Java 代码实战示例,帮助你掌握这一重要设计模式!🚀
📌 1. 为什么需要备忘录模式?(遇到的问题)
❌ 直接保存对象副本的问题
假设我们要在一个文本编辑器中实现“撤销”功能,最简单的方法是每次修改前保存一份对象副本:
java">EditorState backup = editor.clone();
但这样的问题是:
- 增加内存开销:对象副本可能非常大,保存多个状态会占用大量内存。
- 破坏封装性:外部代码必须了解
EditorState
的内部细节,违反了 “信息隐藏” 原则。 - 难以维护:如果对象状态复杂,直接复制副本的方式会让代码变得臃肿。
👉 如何优雅地管理对象的历史状态,同时保持封装性?—— 备忘录模式登场!
📌 2. 备忘录模式是什么?(核心思想)
备忘录模式(Memento Pattern)用于 保存对象的某个历史状态,并在需要时恢复,而不暴露对象的实现细节。
🔹 模式结构
备忘录模式由 三个核心角色 组成:
- 发起人(Originator): 需要保存状态的对象,提供创建和恢复备忘录的方法。
- 备忘录(Memento): 负责存储发起人的内部状态,保证状态的封装性。
- 管理者(Caretaker): 负责存储多个备忘录,并在需要时提供恢复功能。
📌 3. 备忘录模式的 Java 实现(代码实战)
🎯 需求:
我们实现一个文本编辑器,可以保存多个状态,并支持**撤销(Undo)**功能。
🚀 Step 1:定义 Memento
(备忘录类)
java">// 备忘录类:存储 Editor 的状态
class Memento {private final String text;public Memento(String text) {this.text = text;}public String getText() {return text;}
}
🚀 Step 2:定义 Editor
(发起人)
java">// 发起人:文本编辑器,创建和恢复备忘录
class Editor {private String text = "";public void type(String words) {text += words;}public String getText() {return text;}// 创建备忘录public Memento save() {return new Memento(text);}// 恢复备忘录public void restore(Memento memento) {this.text = memento.getText();}
}
🚀 Step 3:定义 Caretaker
(管理者)
java">import java.util.Stack;// 管理者:负责存储和恢复备忘录
class Caretaker {private Stack<Memento> history = new Stack<>();public void saveState(Editor editor) {history.push(editor.save());}public void undo(Editor editor) {if (!history.isEmpty()) {editor.restore(history.pop());}}
}
🚀 Step 4:测试备忘录模式
java">public class MementoPatternDemo {public static void main(String[] args) {Editor editor = new Editor();Caretaker caretaker = new Caretaker();// 输入文字editor.type("Hello, ");caretaker.saveState(editor);editor.type("World!");caretaker.saveState(editor);System.out.println("当前内容:" + editor.getText()); // Hello, World!// 撤销一次caretaker.undo(editor);System.out.println("撤销一次:" + editor.getText()); // Hello, // 再次撤销caretaker.undo(editor);System.out.println("撤销两次:" + editor.getText()); // (空)}
}
📌 运行结果:
当前内容:Hello, World!
撤销一次:Hello,
撤销两次:
📌 4. 备忘录模式的应用场景
✅ 适用于:
- 文本编辑器的撤销/重做(如 Word、IDE 代码编辑器)
- 游戏存档/回档(如 RPG 游戏)
- 数据库事务回滚(如 Hibernate 的
savepoint
机制) - Web 浏览器的历史记录
❌ 不适用于:
- 对象状态过于庞大(可能消耗大量内存)
- 状态变化过于频繁(存储和恢复的成本较高)
📌 5. 备忘录模式在 JDK & Spring 中的应用
🔹 JDK 中的应用
- Java 的
Serializable
接口 允许对象序列化成字节流,从而保存和恢复状态。 java.util.Stack
作为Caretaker
,可以存储多个状态,实现撤销功能。
🔹 Spring 框架中的应用
- Spring 事务管理(Transaction Management)
- Spring 通过
TransactionManager
维护数据库的回滚点,类似于备忘录模式中的Caretaker
。 - 事务的
savepoint
机制允许在事务中存储多个回滚点,并在异常发生时恢复。
- Spring 通过
📌 6. 备忘录模式的优缺点
✅ 优点
✔ 封装性好:状态存储在 Memento
中,不影响 Originator
的实现细节。
✔ 支持撤销/回滚:可以轻松地恢复历史状态。
✔ 解耦管理者与发起人:Caretaker
仅管理 Memento
,不需要了解 Originator
细节。
❌ 缺点
❌ 可能消耗大量内存:如果对象状态过大,存储多个 Memento
可能导致内存占用过高。
❌ 状态变化过快时效率低:如果状态变化频繁,存储和恢复 Memento
的操作可能会影响性能。
📌 7. 总结
- 备忘录模式适用于需要保存和恢复历史状态的场景,如撤销、游戏存档、事务回滚等。
- 它封装了对象状态,保持封装性,避免直接暴露内部数据。
- 在 JDK 和 Spring 事务管理中都有类似应用,值得深入理解和运用!
💡 你是否在项目中用过类似的设计?欢迎留言交流!🚀