备忘录模式
大约 10 分钟
备忘录模式
什么是备忘录模式
备忘录模式(Memento Pattern)是一种行为型设计模式,它在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便日后可以将该对象恢复到原先保存的状态。备忘录模式也被称为快照模式(Snapshot Pattern)。
备忘录模式的核心思想是:
- 在不破坏对象封装性的前提下保存对象的内部状态
- 可以在需要时将对象恢复到原先保存的状态
- 提供了一种状态恢复的实现机制
为什么需要备忘录模式
在实际开发中,我们经常需要实现撤销操作(Undo)或恢复操作(Redo)功能,比如文本编辑器的撤销功能、游戏的存档功能、数据库事务的回滚功能等。如果直接在原对象中保存历史状态,会破坏对象的封装性,而且可能导致对象变得臃肿。备忘录模式通过引入备忘录对象来保存原对象的状态,既保证了对象的封装性,又实现了状态的保存和恢复。
备忘录模式的结构
备忘录模式包含以下几个角色:
- 发起人(Originator):记录当前时刻的内部状态,负责创建和恢复备忘录数据
- 备忘录(Memento):负责存储发起人对象的内部状态,在需要的时候提供发起人需要的内部状态
- 管理者(Caretaker):负责保存备忘录,不能对备忘录的内容进行访问或操作
备忘录模式的实现
基本实现
// 备忘录类
class Memento {
private String state;
public Memento(String state) {
this.state = state;
}
public String getState() {
return state;
}
}
// 发起人类
class Originator {
private String state;
public void setState(String state) {
this.state = state;
}
public String getState() {
return state;
}
// 创建备忘录
public Memento createMemento() {
return new Memento(state);
}
// 从备忘录恢复状态
public void restoreMemento(Memento memento) {
this.state = memento.getState();
}
@Override
public String toString() {
return "Originator{state='" + state + "'}";
}
}
// 管理者类
class Caretaker {
private Memento memento;
public void setMemento(Memento memento) {
this.memento = memento;
}
public Memento getMemento() {
return memento;
}
}
// 使用示例
public class MementoDemo {
public static void main(String[] args) {
// 创建发起人
Originator originator = new Originator();
// 创建管理者
Caretaker caretaker = new Caretaker();
// 设置发起人状态
originator.setState("状态1");
System.out.println("初始状态: " + originator);
// 保存状态
caretaker.setMemento(originator.createMemento());
System.out.println("保存状态");
// 修改状态
originator.setState("状态2");
System.out.println("修改后状态: " + originator);
// 恢复状态
originator.restoreMemento(caretaker.getMemento());
System.out.println("恢复后状态: " + originator);
}
}
改进的实现(保护备忘录内容)
// 改进的备忘录类(窄接口)
class Memento {
private String state;
// 包级私有构造函数,只允许同包的发起人访问
Memento(String state) {
this.state = state;
}
// 包级私有方法,只允许同包的发起人访问
String getState() {
return state;
}
}
// 改进的发起人类
class Originator {
private String state;
public void setState(String state) {
this.state = state;
}
public String getState() {
return state;
}
// 创建备忘录
public Memento createMemento() {
return new Memento(state);
}
// 从备忘录恢复状态
public void restoreMemento(Memento memento) {
this.state = memento.getState();
}
@Override
public String toString() {
return "Originator{state='" + state + "'}";
}
}
// 管理者类(只能保存备忘录,不能访问其内容)
class Caretaker {
private Memento memento;
public void setMemento(Memento memento) {
this.memento = memento;
}
public Memento getMemento() {
return memento;
}
}
// 使用示例
public class ImprovedMementoDemo {
public static void main(String[] args) {
// 创建发起人
Originator originator = new Originator();
// 创建管理者
Caretaker caretaker = new Caretaker();
// 设置发起人状态
originator.setState("初始状态");
System.out.println("初始状态: " + originator);
// 保存状态
caretaker.setMemento(originator.createMemento());
System.out.println("保存状态");
// 修改状态
originator.setState("修改后的状态");
System.out.println("修改后状态: " + originator);
// 恢复状态
originator.restoreMemento(caretaker.getMemento());
System.out.println("恢复后状态: " + originator);
}
}
备忘录模式的应用场景
- 文本编辑器:实现撤销/重做功能
- 游戏存档:保存游戏进度,支持读档
- 数据库事务:实现事务回滚功能
- 浏览器历史:实现后退/前进功能
- IDE调试器:保存程序执行状态,支持断点恢复
- 绘图软件:实现撤销绘制功能
备忘录模式的优缺点
优点
- 保护封装性:备忘录模式将状态的保存和恢复细节封装在发起人内部
- 简化发起人类:发起人不需要管理和保存其历史状态
- 支持撤销操作:可以方便地实现撤销和恢复功能
- 符合单一职责原则:每个类只负责自己的职责
缺点
- 消耗资源:如果需要保存的状态较多,会消耗大量内存
- 破坏封装性:在某些实现中,备忘录可能会暴露发起人的内部状态
- 管理复杂:需要管理多个备忘录对象的生命周期
备忘录模式与其他模式的比较
与命令模式的区别
- 备忘录模式:关注对象状态的保存和恢复
- 命令模式:关注将操作封装成对象,支持撤销操作
与原型模式的区别
- 备忘录模式:保存对象的状态以便恢复
- 原型模式:通过复制对象来创建新对象
与状态模式的区别
- 备忘录模式:保存和恢复对象的状态
- 状态模式:对象的行为随着状态的改变而改变
实际项目中的应用
// 文本编辑器示例
// 文本编辑器状态类
class TextEditorMemento {
private String content;
private int cursorPosition;
// 包级私有构造函数
TextEditorMemento(String content, int cursorPosition) {
this.content = content;
this.cursorPosition = cursorPosition;
}
// 包级私有方法
String getContent() {
return content;
}
int getCursorPosition() {
return cursorPosition;
}
}
// 文本编辑器类
class TextEditor {
private String content = "";
private int cursorPosition = 0;
public void setContent(String content) {
this.content = content;
}
public String getContent() {
return content;
}
public void setCursorPosition(int cursorPosition) {
this.cursorPosition = cursorPosition;
}
public int getCursorPosition() {
return cursorPosition;
}
// 输入文本
public void insertText(String text) {
content = content.substring(0, cursorPosition) + text + content.substring(cursorPosition);
cursorPosition += text.length();
}
// 删除文本
public void deleteText(int count) {
if (cursorPosition >= count) {
content = content.substring(0, cursorPosition - count) + content.substring(cursorPosition);
cursorPosition -= count;
}
}
// 创建备忘录
public TextEditorMemento createMemento() {
return new TextEditorMemento(content, cursorPosition);
}
// 从备忘录恢复状态
public void restoreMemento(TextEditorMemento memento) {
this.content = memento.getContent();
this.cursorPosition = memento.getCursorPosition();
}
@Override
public String toString() {
return "TextEditor{content='" + content + "', cursorPosition=" + cursorPosition + "}";
}
}
// 历史记录管理器
class HistoryManager {
private Stack<TextEditorMemento> history = new Stack<>();
public void saveMemento(TextEditorMemento memento) {
history.push(memento);
}
public TextEditorMemento getMemento() {
if (!history.isEmpty()) {
return history.pop();
}
return null;
}
public boolean hasHistory() {
return !history.isEmpty();
}
}
// 文本编辑器应用
public class TextEditorDemo {
public static void main(String[] args) {
// 创建文本编辑器
TextEditor editor = new TextEditor();
// 创建历史记录管理器
HistoryManager historyManager = new HistoryManager();
// 模拟编辑操作
System.out.println("=== 文本编辑器操作演示 ===");
// 输入文本
editor.insertText("Hello");
System.out.println("1. 输入'Hello': " + editor);
historyManager.saveMemento(editor.createMemento());
editor.insertText(" World");
System.out.println("2. 输入' World': " + editor);
historyManager.saveMemento(editor.createMemento());
editor.insertText("!");
System.out.println("3. 输入'!': " + editor);
historyManager.saveMemento(editor.createMemento());
// 删除文本
editor.deleteText(1);
System.out.println("4. 删除1个字符: " + editor);
historyManager.saveMemento(editor.createMemento());
// 撤销操作
System.out.println("\n=== 撤销操作 ===");
while (historyManager.hasHistory()) {
TextEditorMemento memento = historyManager.getMemento();
if (memento != null) {
editor.restoreMemento(memento);
System.out.println("恢复到上一状态: " + editor);
}
}
}
}
// 游戏存档示例
// 游戏角色状态类
class GameMemento {
private int level;
private int score;
private int health;
private List<String> inventory;
// 包级私有构造函数
GameMemento(int level, int score, int health, List<String> inventory) {
this.level = level;
this.score = score;
this.health = health;
// 创建副本以保护原始数据
this.inventory = new ArrayList<>(inventory);
}
// 包级私有方法
int getLevel() {
return level;
}
int getScore() {
return score;
}
int getHealth() {
return health;
}
List<String> getInventory() {
return new ArrayList<>(inventory); // 返回副本
}
}
// 游戏角色类
class GameCharacter {
private int level = 1;
private int score = 0;
private int health = 100;
private List<String> inventory = new ArrayList<>();
// 升级
public void levelUp() {
level++;
score += 100;
System.out.println("升级到 " + level + " 级");
}
// 获得分数
public void addScore(int points) {
score += points;
System.out.println("获得 " + points + " 分,总分: " + score);
}
// 受伤
public void takeDamage(int damage) {
health -= damage;
if (health < 0) health = 0;
System.out.println("受到 " + damage + " 点伤害,剩余生命值: " + health);
}
// 治疗
public void heal(int healAmount) {
health += healAmount;
if (health > 100) health = 100;
System.out.println("恢复 " + healAmount + " 点生命值,当前生命值: " + health);
}
// 获得物品
public void addItem(String item) {
inventory.add(item);
System.out.println("获得物品: " + item);
}
// 使用物品
public void useItem(String item) {
if (inventory.remove(item)) {
System.out.println("使用物品: " + item);
} else {
System.out.println("没有物品: " + item);
}
}
// 创建存档
public GameMemento createMemento() {
return new GameMemento(level, score, health, inventory);
}
// 从存档恢复
public void restoreMemento(GameMemento memento) {
this.level = memento.getLevel();
this.score = memento.getScore();
this.health = memento.getHealth();
this.inventory = memento.getInventory();
System.out.println("从存档恢复游戏状态");
}
@Override
public String toString() {
return "GameCharacter{level=" + level + ", score=" + score +
", health=" + health + ", inventory=" + inventory + "}";
}
// Getter方法
public int getLevel() { return level; }
public int getScore() { return score; }
public int getHealth() { return health; }
public List<String> getInventory() { return new ArrayList<>(inventory); }
}
// 存档管理器
class SaveManager {
private Map<String, GameMemento> saves = new HashMap<>();
public void saveGame(String saveName, GameMemento memento) {
saves.put(saveName, memento);
System.out.println("游戏已保存到存档: " + saveName);
}
public GameMemento loadGame(String saveName) {
GameMemento memento = saves.get(saveName);
if (memento != null) {
System.out.println("从存档 " + saveName + " 加载游戏");
} else {
System.out.println("找不到存档: " + saveName);
}
return memento;
}
public void listSaves() {
System.out.println("可用存档: " + saves.keySet());
}
}
// 游戏演示
public class GameDemo {
public static void main(String[] args) {
// 创建游戏角色
GameCharacter character = new GameCharacter();
// 创建存档管理器
SaveManager saveManager = new SaveManager();
// 模拟游戏过程
System.out.println("=== 游戏过程演示 ===");
// 游戏开始
System.out.println("1. 游戏开始: " + character);
// 角色升级和获得物品
character.levelUp();
character.addScore(50);
character.addItem("剑");
character.addItem("药水");
System.out.println("2. 游戏进行中: " + character);
// 保存游戏
saveManager.saveGame("save1", character.createMemento());
// 继续游戏
character.levelUp();
character.addScore(80);
character.takeDamage(30);
character.addItem("盾牌");
System.out.println("3. 继续游戏: " + character);
// 保存游戏
saveManager.saveGame("save2", character.createMemento());
// 遇到强敌
character.takeDamage(80);
System.out.println("4. 遇到强敌后: " + character);
// 读取存档
System.out.println("\n=== 读取存档 ===");
GameMemento save1 = saveManager.loadGame("save1");
if (save1 != null) {
character.restoreMemento(save1);
System.out.println("5. 读取存档后: " + character);
}
// 再次保存
saveManager.saveGame("save3", character.createMemento());
// 查看所有存档
saveManager.listSaves();
}
}
Java中的备忘录模式应用
// Java序列化实现备忘录模式
import java.io.*;
// 可序列化的游戏角色类
class SerializableGameCharacter implements Serializable {
private static final long serialVersionUID = 1L;
private int level;
private int score;
private int health;
private List<String> inventory;
public SerializableGameCharacter() {
this.level = 1;
this.score = 0;
this.health = 100;
this.inventory = new ArrayList<>();
}
// 游戏操作方法
public void levelUp() {
level++;
score += 100;
System.out.println("升级到 " + level + " 级");
}
public void addScore(int points) {
score += points;
System.out.println("获得 " + points + " 分,总分: " + score);
}
public void addItem(String item) {
inventory.add(item);
System.out.println("获得物品: " + item);
}
// 保存状态到文件(备忘录模式)
public void saveState(String filename) throws IOException {
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filename))) {
oos.writeObject(this);
System.out.println("游戏状态已保存到: " + filename);
}
}
// 从文件恢复状态(备忘录模式)
public static SerializableGameCharacter loadState(String filename) throws IOException, ClassNotFoundException {
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename))) {
SerializableGameCharacter character = (SerializableGameCharacter) ois.readObject();
System.out.println("从 " + filename + " 恢复游戏状态");
return character;
}
}
@Override
public String toString() {
return "SerializableGameCharacter{level=" + level + ", score=" + score +
", health=" + health + ", inventory=" + inventory + "}";
}
// Getter方法
public int getLevel() { return level; }
public int getScore() { return score; }
public int getHealth() { return health; }
public List<String> getInventory() { return new ArrayList<>(inventory); }
}
// 使用示例
public class SerializableMementoDemo {
public static void main(String[] args) {
try {
// 创建游戏角色
SerializableGameCharacter character = new SerializableGameCharacter();
// 游戏操作
System.out.println("=== 序列化备忘录模式演示 ===");
System.out.println("1. 初始状态: " + character);
character.levelUp();
character.addScore(50);
character.addItem("魔法书");
System.out.println("2. 游戏进行中: " + character);
// 保存状态
character.saveState("game_save.dat");
// 继续游戏
character.levelUp();
character.addScore(80);
character.addItem("法杖");
System.out.println("3. 继续游戏: " + character);
// 从文件恢复状态
SerializableGameCharacter restoredCharacter = SerializableGameCharacter.loadState("game_save.dat");
System.out.println("4. 恢复状态: " + restoredCharacter);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
总结
备忘录模式是一种非常实用的行为型设计模式,它可以在不破坏对象封装性的前提下,保存对象的内部状态,并在需要时将对象恢复到原先保存的状态。在实际开发中,当我们需要实现撤销操作、存档功能或状态恢复机制时,备忘录模式是一个很好的选择。
使用备忘录模式的关键点:
- 识别需要保存和恢复状态的对象
- 设计备忘录类来保存对象的内部状态
- 在发起人中实现创建和恢复备忘录的方法
- 设计管理者类来管理备忘录对象
- 注意保护备忘录的封装性,避免外部直接访问其内容
备忘录模式的优点是能够保护对象的封装性,简化发起人类的设计,支持撤销操作。但需要注意的是,如果需要保存的状态较多,会消耗大量内存资源。在现代Java开发中,备忘录模式广泛应用于文本编辑器、游戏、数据库事务等需要状态保存和恢复的场景。