备忘录模式:对象状态的时空穿梭者
摘要
备忘录模式(Memento Pattern)是行为型设计模式中的"状态保存专家",它提供了在不破坏封装性的前提下捕获并外部化对象的内部状态。本文将全面剖析备忘录模式的核心概念、实现方式、应用场景及高级变体,通过丰富的Java代码示例展示如何实现对象状态的保存与恢复,并分析其与命令模式、原型模式的区别与适用场景。
一、备忘录模式核心思想
备忘录模式的核心是状态捕获与恢复,具有以下关键特征:
- 状态封装:将对象状态保存在独立备忘录中
- 不破坏封装:不暴露对象内部实现细节
- 历史管理:支持多状态保存与选择性恢复
- 撤销支持:实现撤销操作的核心模式
适用场景:
- 需要保存对象状态快照供后续恢复
- 需要实现撤销/重做功能
- 需要提供对象状态的历史记录
- 直接获取对象状态会暴露实现细节
二、备忘录模式结构解析
UML类图示意
[Originator] <--> [Memento]
[Caretaker] o--> [Memento]
核心组件角色
| 角色 |
职责 |
典型实现 |
| Originator |
原发器 |
需要保存状态的对象 |
| Memento |
备忘录 |
保存原发器状态的对象 |
| Caretaker |
管理者 |
负责保存和管理备忘录 |
三、基础实现:文本编辑器案例
// 备忘录:保存编辑器状态
class EditorMemento {
private final String content;
private final int cursorPosition;
public EditorMemento(String content, int cursorPosition) {
this.content = content;
this.cursorPosition = cursorPosition;
}
public String getContent() {
return content;
}
public int getCursorPosition() {
return cursorPosition;
}
}
// 原发器:文本编辑器
class TextEditor {
private String content;
private int cursorPosition;
public void type(String text) {
content = content == null ? text : content + text;
cursorPosition += text.length();
}
public void setCursor(int position) {
this.cursorPosition = position;
}
public EditorMemento save() {
return new EditorMemento(content, cursorPosition);
}
public void restore(EditorMemento memento) {
this.content = memento.getContent();
this.cursorPosition = memento.getCursorPosition();
}
public void print() {
System.out.println("Content: " + content);
System.out.println("Cursor at: " + cursorPosition);
}
}
// 管理者:历史记录
class History {
private Stack<EditorMemento> states = new Stack<>();
public void push(EditorMemento memento) {
states.push(memento);
}
public EditorMemento pop() {
return states.pop();
}
public boolean isEmpty() {
return states.isEmpty();
}
}
// 客户端使用
public class TextEditorDemo {
public static void main(String[] args) {
TextEditor editor = new TextEditor();
History history = new History();
editor.type("Hello");
history.push(editor.save());
editor.print();
editor.type(" World");
history.push(editor.save());
editor.print();
System.out.println("\nUndo last change:");
editor.restore(history.pop());
editor.print();
System.out.println("\nUndo again:");
editor.restore(history.pop());
editor.print();
}
}
四、高级应用:事务回滚系统
1. 数据库事务回滚
// 备忘录:事务状态
class TransactionMemento {
private final Map<String, Object> state;
public TransactionMemento(Map<String, Object> state) {
this.state = new HashMap<>(state);
}
public Map<String, Object> getState() {
return new HashMap<>(state);
}
}
// 原发器:数据库表
class DatabaseTable {
private Map<String, Object> data = new HashMap<>();
private String tableName;
public DatabaseTable(String tableName) {
this.tableName = tableName;
}
public void insert(String key, Object value) {
data.put(key, value);
System.out.println("Inserted: " + key + "=" + value);
}
public void delete(String key) {
data.remove(key);
System.out.println("Deleted: " + key);
}
public TransactionMemento saveState() {
return new TransactionMemento(data);
}
public void restoreState(TransactionMemento memento) {
this.data = memento.getState();
}
public void print() {
System.out.println(tableName + " contents:");
data.forEach((k, v) -> System.out.println(k + ": " + v));
}
}
// 管理者:事务管理器
class TransactionManager {
private Stack<TransactionMemento> history = new Stack<>();
public void beginTransaction(DatabaseTable table) {
history.push(table.saveState());
System.out.println("Transaction started");
}
public void rollback(DatabaseTable table) {
if (!history.isEmpty()) {
table.restoreState(history.pop());
System.out.println("Transaction rolled back");
}
}
}
// 使用示例
public class TransactionSystem {
public static void main(String[] args) {
DatabaseTable users = new DatabaseTable("users");
TransactionManager txManager = new TransactionManager();
// 开始事务
txManager.beginTransaction(users);
users.insert("user1", "Alice");
users.insert("user2", "Bob");
// 保存中间状态
txManager.beginTransaction(users);
users.delete("user1");
users.insert("user3", "Charlie");
users.print();
// 回滚到上一个状态
txManager.rollback(users);
users.print();
// 回滚到初始状态
txManager.rollback(users);
users.print();
}
}
2. 游戏存档系统
// 游戏状态备忘录
class GameMemento {
private final int level;
private final int score;
private final List<String> inventory;
public GameMemento(int level, int score, List<String> inventory) {
this.level = level;
this.score = score;
this.inventory = new ArrayList<>(inventory);
}
public int getLevel() {
return level;
}
public int getScore() {
return score;
}
public List<String> getInventory() {
return new ArrayList<>(inventory);
}
}
// 游戏角色(原发器)
class GameCharacter {
private int level;
private int score;
private List<String> inventory = new ArrayList<>();
public void playLevel(int levelGain, int scoreGain, String... items) {
this.level += levelGain;
this.score += scoreGain;
Collections.addAll(inventory, items);
System.out.println("Played level. Current state:");
printStatus();
}
public GameMemento save() {
return new GameMemento(level, score, inventory);
}
public void restore(GameMemento memento) {
this.level = memento.getLevel();
this.score = memento.getScore();
this.inventory = memento.getInventory();
}
public void printStatus() {
System.out.println("Level: " + level);
System.out.println("Score: " + score);
System.out.println("Inventory: " + inventory);
}
}
// 存档管理器(管理者)
class SaveGameManager {
private Map<String, GameMemento> saves = new HashMap<>();
public void saveGame(String saveName, GameCharacter character) {
saves.put(saveName, character.save());
System.out.println("Game saved: " + saveName);
}
public void loadGame(String saveName, GameCharacter character) {
GameMemento memento = saves.get(saveName);
if (memento != null) {
character.restore(memento);
System.out.println("Game loaded: " + saveName);
} else {
System.out.println("Save not found: " + saveName);
}
}
public void listSaves() {
System.out.println("Available saves:");
saves.keySet().forEach(System.out::println);
}
}
// 使用示例
public class GameDemo {
public static void main(String[] args) {
GameCharacter player = new GameCharacter();
SaveGameManager saveManager = new SaveGameManager();
player.playLevel(1, 100, "Sword", "Shield");
saveManager.saveGame("initial", player);
player.playLevel(1, 150, "Potion");
saveManager.saveGame("after_level2", player);
player.playLevel(1, 200, "Key");
System.out.println("\nCurrent state:");
player.printStatus();
System.out.println("\nLoading save:");
saveManager.loadGame("after_level2", player);
player.printStatus();
System.out.println("\nLoading initial save:");
saveManager.loadGame("initial", player);
player.printStatus();
}
}
五、备忘录模式优缺点分析
优点:
| 优点 |
说明 |
| 状态封装 |
不暴露对象内部实现细节 |
| 简化原发器 |
将状态管理职责分离 |
| 撤销支持 |
轻松实现撤销/重做功能 |
| 历史快照 |
支持状态历史记录 |
| 恢复灵活 |
可选择恢复到任意历史状态 |
缺点:
| 缺点 |
说明 |
| 资源消耗 |
保存多个状态可能占用大量内存 |
| 性能开销 |
频繁保存/恢复可能影响性能 |
| 实现复杂 |
需要精心设计备忘录结构 |
| 窄接口挑战 |
平衡封装性与访问权限 |
六、备忘录模式与其他模式对比
备忘录模式 vs 命令模式
| 维度 |
备忘录模式 |
命令模式 |
| 目的 |
保存和恢复状态 |
封装操作请求 |
| 状态管理 |
直接保存对象状态 |
通过执行命令改变状态 |
| 撤销实现 |
通过状态恢复 |
通过逆操作 |
| 适用场景 |
复杂状态恢复 |
操作撤销/重做 |
备忘录模式 vs 原型模式
| 维度 |
备忘录模式 |
原型模式 |
| 目的 |
保存特定状态 |
克隆完整对象 |
| 状态选择 |
可选择保存部分状态 |
总是克隆全部状态 |
| 使用方式 |
外部存储状态 |
创建新对象副本 |
| 性能 |
通常更轻量 |
可能更重 |
七、备忘录模式最佳实践
1. 增量备忘录
// 只保存变化的部分状态
class IncrementalMemento {
private final Map<String, Object> changes;
public IncrementalMemento(Map<String, Object> currentState,
Map<String, Object> previousState) {
this.changes = new HashMap<>();
currentState.forEach((k, v) -> {
if (!v.equals(previousState.get(k))) {
changes.put(k, v);
}
});
}
public void applyTo(Map<String, Object> state) {
changes.forEach(state::put);
}
}
// 使用增量备忘录的原发器
class EfficientOriginator {
private Map<String, Object> state = new HashMap<>();
public void setState(String key, Object value) {
state.put(key, value);
}
public IncrementalMemento save() {
return new IncrementalMemento(state, new HashMap<>(state));
}
public void restore(IncrementalMemento memento) {
memento.applyTo(state);
}
}
2. 备忘录序列化
// 可序列化的备忘录
class SerializableMemento implements Serializable {
private static final long serialVersionUID = 1L;
private final byte[] serializedState;
public SerializableMemento(Object state) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(state);
oos.close();
this.serializedState = baos.toByteArray();
}
public Object getState() throws IOException, ClassNotFoundException {
ByteArrayInputStream bais = new ByteArrayInputStream(serializedState);
ObjectInputStream ois = new ObjectInputStream(bais);
return ois.readObject();
}
}
// 使用示例
class PersistableOriginator {
private Object complexState;
public SerializableMemento save() throws IOException {
return new SerializableMemento(complexState);
}
public void restore(SerializableMemento memento)
throws IOException, ClassNotFoundException {
this.complexState = memento.getState();
}
}
3. 备忘录与访问者模式结合
// 备忘录访问者接口
interface MementoVisitor {
void visitTextMemento(TextMemento memento);
void visitImageMemento(ImageMemento memento);
}
// 复合备忘录
class CompoundMemento {
private List<Object> mementos = new ArrayList<>();
public void addMemento(Object memento) {
mementos.add(memento);
}
public void accept(MementoVisitor visitor) {
mementos.forEach(m -> {
if (m instanceof TextMemento) {
visitor.visitTextMemento((TextMemento) m);
} else if (m instanceof ImageMemento) {
visitor.visitImageMemento((ImageMemento) m);
}
});
}
}
八、备忘录模式在开源框架中的应用
Java Swing的撤销框架
// UndoManager是备忘录模式的管理者
UndoManager undoManager = new UndoManager();
JTextArea textArea = new JTextArea();
// Document是原发器
textArea.getDocument().addUndoableEditListener(e -> {
undoManager.addEdit(e.getEdit());
});
// 撤销操作
Action undoAction = new AbstractAction("Undo") {
public void actionPerformed(ActionEvent evt) {
if (undoManager.canUndo()) {
undoManager.undo(); // 恢复备忘录
}
}
};
// 重做操作
Action redoAction = new AbstractAction("Redo") {
public void actionPerformed(ActionEvent evt) {
if (undoManager.canRedo()) {
undoManager.redo(); // 应用备忘录
}
}
};
Hibernate的实体状态管理
// Hibernate的Session缓存实体状态
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
User user = session.get(User.class, 1L); // 加载状态
user.setName("New Name"); // 修改状态
// 回滚事务会恢复原始状态
tx.rollback();
// 此时user.getName()返回原始值
九、备忘录模式高级应用
1. 分布式备忘录
// 分布式状态管理器
class DistributedMementoManager {
private DistributedCache cache;
public DistributedMementoManager(DistributedCache cache) {
this.cache = cache;
}
public void saveState(String key, Serializable state) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(state);
oos.close();
cache.put(key, baos.toByteArray());
} catch (IOException e) {
throw new RuntimeException("Serialization failed", e);
}
}
public Object loadState(String key) {
byte[] data = cache.get(key);
if (data == null) return null;
try {
ByteArrayInputStream bais = new ByteArrayInputStream(data);
ObjectInputStream ois = new ObjectInputStream(bais);
return ois.readObject();
} catch (Exception e) {
throw new RuntimeException("Deserialization failed", e);
}
}
}
// 使用示例
DistributedCache redis = new RedisCache();
DistributedMementoManager manager = new DistributedMementoManager(redis);
// 保存状态
manager.saveState("user:1:backup", user);
// 恢复状态
User restoredUser = (User) manager.loadState("user:1:backup");
2. 时间旅行调试器
// 调试状态备忘录
class DebugStateMemento {
private final Map<String, Object> variables;
private final int lineNumber;
private final String stackTrace;
public DebugStateMemento(Map<String, Object> variables,
int lineNumber, String stackTrace) {
this.variables = new HashMap<>(variables);
this.lineNumber = lineNumber;
this.stackTrace = stackTrace;
}
// getters...
}
// 调试上下文
class DebugContext {
private List<DebugStateMemento> history = new ArrayList<>();
private int currentIndex = -1;
public void captureState(Map<String, Object> variables,
int lineNumber, String stackTrace) {
DebugStateMemento memento =
new DebugStateMemento(variables, lineNumber, stackTrace);
history.add(memento);
currentIndex = history.size() - 1;
}
public DebugStateMemento stepBack() {
if (currentIndex > 0) {
return history.get(--currentIndex);
}
return null;
}
public DebugStateMemento stepForward() {
if (currentIndex < history.size() - 1) {
return history.get(++currentIndex);
}
return null;
}
}
// 使用示例
DebugContext debugger = new DebugContext();
// 在断点处捕获状态
Map<String, Object> vars = getCurrentVariables();
debugger.captureState(vars, currentLine, getStackTrace());
// 时间旅行调试
DebugStateMemento previousState = debugger.stepBack();
displayState(previousState);
十、备忘录模式未来发展趋势
新兴应用方向:
- 微服务架构:跨服务的状态恢复
- 容器技术:应用快照与恢复
- 区块链:智能合约状态回滚
- AI训练:模型训练状态保存
- VR/AR:虚拟环境状态持久化
响应式备忘录模式
// Reactor中的状态恢复
Mono<DatabaseState> saveState(Database db) {
return Mono.fromCallable(() -> {
return new DatabaseState(db); // 创建备忘录
}).subscribeOn(Schedulers.boundedElastic());
}
Flux<DatabaseState> stateHistory = Flux.just(
saveState(db),
saveState(dbAfterUpdate),
saveState(dbAfterAnotherUpdate)
).flatMap(Function.identity());
// 恢复到特定状态
stateHistory.elementAt(1)
.subscribe(state -> state.restore(db));
总结
备忘录模式是管理对象状态变化的强大工具,特别适合需要状态历史管理的场景。其核心价值体现在:
- 状态封装:保护对象内部状态不被直接访问
- 历史管理:支持多状态保存与选择性恢复
- 撤销支持:实现撤销操作的基础模式
- 架构清晰:分离状态管理职责
现代应用关键点:
- 状态粒度:合理设计备忘录保存的状态范围
- 性能优化:考虑增量保存和懒加载
- 分布式支持:适应微服务和云原生架构
- 序列化安全:确保状态序列化的可靠性
- 内存管理:控制历史状态的内存占用
备忘录模式正在与分布式系统、响应式编程等现代技术结合,演进出更强大的状态管理解决方案。掌握备忘录模式的精髓,将帮助开发者构建出更加健壮、可维护的系统架构,特别是在需要复杂状态管理的领域。