备忘录模式:实现对象状态撤销与恢复的设计模式
备忘录模式:实现对象状态撤销与恢复的设计模式
一、模式核心:在不破坏封装性的前提下保存和恢复对象状态
在软件开发中,经常需要实现 “撤销” 功能(如文本编辑器的撤销修改、游戏存档读取)。直接暴露对象内部状态会破坏封装性,而备忘录模式通过独立的备忘录对象封装状态,实现安全的状态管理。
备忘录模式(Memento Pattern) 允许在不暴露对象内部细节的情况下,捕获对象的内部状态并保存为备忘录(Memento),后续可通过备忘录恢复对象状态。核心解决:
- 状态封装:备忘录对象封装对象状态,避免外部直接访问内部属性。
- 撤销 / 重做支持:通过保存多个备忘录实现多步撤销(如版本控制)。
- 单一职责分离:将状态管理逻辑从原发对象中分离,符合开闭原则。
核心思想与 UML 类图(PlantUML 语法)
备忘录模式包含以下角色:
- 原发器(Originator):创建并恢复自身状态的对象。
- 备忘录(Memento):存储原发器的状态,提供有限访问接口。
- 管理者(Caretaker):管理备忘录的创建、存储和获取(如历史记录列表)。
二、核心实现:文本编辑器的撤销功能
1. 定义原发器(文本编辑器)
public class TextEditor { private String content; // 编辑内容 // 创建备忘录(保存当前状态) public Memento createMemento() { return new Memento(content); } // 恢复状态(从备忘录中读取) public void restoreMemento(Memento memento) { this.content = memento.getState(); } // 修改内容(模拟编辑操作) public void append(String text) { content = (content != null ? content : "") + text; } // 获取当前内容 public String getContent() { return content; }
}
2. 定义备忘录(包可见,隐藏状态访问)
class Memento { private final String state; Memento(String state) { this.state = state; } // 包可见方法,仅同一包内的原发器可调用 String getState() { return state; }
}
3. 定义管理者(保存历史记录)
import java.util.ArrayList;
import java.util.List; public class HistoryManager { private final List<Memento> mementoList = new ArrayList<>(); // 添加新备忘录到历史记录 public void saveMemento(Memento memento) { mementoList.add(memento); } // 获取指定版本的备忘录(索引从 0 开始) public Memento getMemento(int index) { return mementoList.get(index); }
}
4. 客户端使用备忘录模式
public class ClientDemo { public static void main(String[] args) { TextEditor editor = new TextEditor(); HistoryManager history = new HistoryManager(); // 编辑步骤 1:输入 "Hello" editor.append("Hello"); System.out.println("当前内容:" + editor.getContent()); // 输出:Hello history.saveMemento(editor.createMemento()); // 保存状态 1 // 编辑步骤 2:添加 " World!" editor.append(" World!"); System.out.println("当前内容:" + editor.getContent()); // 输出:Hello World! history.saveMemento(editor.createMemento()); // 保存状态 2 // 撤销到第一步 editor.restoreMemento(history.getMemento(0)); System.out.println("撤销后内容:" + editor.getContent()); // 输出:Hello }
}
输出结果:
当前内容:Hello
当前内容:Hello World!
撤销后内容:Hello
三、进阶:实现多步撤销与状态快照
通过在管理者中维护备忘录列表,支持回滚到任意历史版本(如版本控制系统)。
1. 扩展管理者支持版本索引
public class AdvancedHistoryManager { private final List<Memento> mementoList = new ArrayList<>(); private int currentIndex = -1; // 当前版本索引 // 保存新状态并清除后续版本(如重做后新增修改) public void saveMemento(Memento memento) { currentIndex++; if (currentIndex < mementoList.size()) { mementoList.set(currentIndex, memento); // 覆盖旧版本 } else { mementoList.add(memento); // 添加新版本 } } // 撤销(回退到上一版本) public Memento undo() { if (currentIndex > 0) { currentIndex--; return mementoList.get(currentIndex); } return null; } // 重做(前进到下一版本) public Memento redo() { if (currentIndex < mementoList.size() - 1) { currentIndex++; return mementoList.get(currentIndex); } return null; }
}
2. 客户端测试多步撤销 / 重做
public class ClientDemo { public static void main(String[] args) { AdvancedHistoryManager history = new AdvancedHistoryManager(); TextEditor editor = new TextEditor(); // 编辑并保存三个版本 editor.append("A"); history.saveMemento(editor.createMemento()); // 版本 0: "A" editor.append("B"); history.saveMemento(editor.createMemento()); // 版本 1: "AB" editor.append("C"); history.saveMemento(editor.createMemento()); // 版本 2: "ABC" // 撤销两次到版本 0 history.undo(); // 版本 1 history.undo(); // 版本 0 System.out.println("撤销两次后:" + editor.getContent()); // 输出:"A" // 重做一次到版本 1 editor.restoreMemento(history.redo()); System.out.println("重做一次后:" + editor.getContent()); // 输出:"AB" }
}
四、框架与源码中的备忘录实践
1. Java 的 java.io.Serializable
对象序列化可视为备忘录模式的一种实现:通过序列化保存对象状态(备忘录),反序列化恢复状态(如分布式系统中的 checkpoint)。
2. Git 版本控制
Git 通过提交(Commit)保存代码快照(备忘录),允许回滚到任意历史版本(git checkout
/git revert
),本质上是备忘录模式的应用。
3. Eclipse 的撤销功能
Eclipse 编辑器通过备忘录模式保存代码修改历史,支持多步撤销(Ctrl+Z
)和重做(Ctrl+Y
),每个修改版本对应一个备忘录。
五、避坑指南:正确使用备忘录模式的 3 个要点
1. 控制备忘录的访问权限
备忘录的状态访问方法应设为包可见(默认权限)或私有,避免外部直接修改状态,确保仅原发器可恢复状态。
2. 处理大状态的性能问题
若对象状态包含大量数据(如图像、大文件),备忘录会占用大量内存。可采用原型模式克隆轻量级状态,或虚拟备忘录仅记录差异(如命令模式中的增量保存)。
3. 避免内存泄漏
管理者需合理管理备忘录列表,及时清理不再需要的历史记录(如限制最大版本数),防止内存溢出。
六、总结:何时该用备忘录模式?
适用场景 | 核心特征 | 典型案例 |
---|---|---|
撤销 / 重做功能 | 需要记录对象状态变化,支持回滚 | 文本编辑器、绘图软件 |
状态备份与恢复 | 定期保存状态快照,支持故障恢复 | 游戏存档、数据库备份 |
版本控制 | 需要管理对象的多个历史版本 | 代码版本管理、文档修订追踪 |
备忘录模式通过封装状态管理逻辑,在不破坏封装性的前提下实现了灵活的状态恢复机制。下一篇我们将探讨中介者模式,解析如何解耦对象间的复杂交互,敬请期待!
扩展思考:备忘录模式 vs 命令模式
类型 | 核心功能 | 协作方式 |
---|---|---|
备忘录模式 | 保存 / 恢复对象状态 | 原发器创建备忘录,管理者存储 |
命令模式 | 封装操作命令,支持撤销 / 重做 | 命令对象记录操作前后状态 |
组合使用 | 命令模式调用备忘录模式保存操作状态,实现多步撤销 | 命令执行时创建备忘录,撤销时恢复 |