备忘录模式 (Memento Pattern)
概述
备忘录模式是一种行为型设计模式,它允许在不破坏封装性的前提下捕获一个对象的内部状态,并在该对象之外保存这个状态,以便在需要时恢复到原先保存的状态。
备忘录模式的核心思想是通过创建一个备忘录对象来存储原发器(Originator)对象的内部状态,而备忘录对象只能由原发器对象创建和读取,其他对象只能保存和传递备忘录对象,不能修改其内容。这样可以保证对象状态的封装性和完整性。
核心要点
- 状态保存:在不破坏封装性的前提下保存对象的内部状态
- 可恢复性:允许对象恢复到之前保存的状态
- 封装性:备忘录对象的内容只能由原发器访问和修改
- 历史记录:可以保存对象的多个历史状态,支持多次撤销和重做
应用场景
- 需要实现撤销/重做功能时:如文本编辑器、图形编辑软件等
- 需要保存对象状态的快照时:如游戏存档、配置文件备份等
- 需要防止对象外部直接访问和修改其内部状态时:保护对象的封装性
- 需要实现状态历史记录和回溯功能时:如版本控制系统
结构
备忘录模式包含以下角色:
- 原发器(Originator):创建备忘录对象并记录其当前内部状态,也可以使用备忘录对象恢复内部状态
- 备忘录(Memento):存储原发器对象的内部状态,只允许原发器对象访问其状态
- 管理者(Caretaker):保存备忘录对象,但不能操作或检查备忘录对象的内容
实现示例
1. 基本备忘录模式实现
java
// 备忘录类
public class Memento {
private String state; // 存储的状态
private String date; // 创建日期
// 私有构造函数,只允许Originator创建
protected Memento(String state) {
this.state = state;
this.date = java.time.LocalDateTime.now().toString();
}
// 只允许Originator访问状态
protected String getState() {
return state;
}
// 获取创建日期(可选)
public String getDate() {
return date;
}
@Override
public String toString() {
return "备忘录[状态='" + state + "', 创建时间='" + date + "']";
}
}
// 原发器类
public class Originator {
private String state; // 内部状态
// 设置状态
public void setState(String state) {
System.out.println("设置状态为: " + state);
this.state = state;
}
// 获取状态
public String getState() {
return state;
}
// 创建备忘录对象,保存当前状态
public Memento createMemento() {
System.out.println("创建备忘录,保存当前状态");
return new Memento(state);
}
// 从备忘录对象恢复状态
public void restoreFromMemento(Memento memento) {
if (memento != null) {
this.state = memento.getState();
System.out.println("从备忘录恢复状态为: " + state);
}
}
}
// 管理者类
public class Caretaker {
private List<Memento> mementoList = new ArrayList<>(); // 保存备忘录列表
private int currentIndex = -1; // 当前状态索引
// 添加备忘录
public void addMemento(Memento memento) {
// 移除当前索引之后的所有备忘录(如果有)
if (currentIndex < mementoList.size() - 1) {
mementoList = new ArrayList<>(mementoList.subList(0, currentIndex + 1));
}
// 添加新备忘录
mementoList.add(memento);
currentIndex = mementoList.size() - 1;
System.out.println("添加备忘录,当前索引: " + currentIndex);
}
// 获取上一个备忘录(撤销)
public Memento undo() {
if (currentIndex > 0) {
currentIndex--;
System.out.println("撤销操作,当前索引: " + currentIndex);
return mementoList.get(currentIndex);
} else {
System.out.println("已经是最早的状态,无法撤销");
return null;
}
}
// 获取下一个备忘录(重做)
public Memento redo() {
if (currentIndex < mementoList.size() - 1) {
currentIndex++;
System.out.println("重做操作,当前索引: " + currentIndex);
return mementoList.get(currentIndex);
} else {
System.out.println("已经是最新的状态,无法重做");
return null;
}
}
// 获取当前备忘录
public Memento getCurrentMemento() {
if (currentIndex >= 0 && currentIndex < mementoList.size()) {
return mementoList.get(currentIndex);
}
return null;
}
// 获取备忘录历史
public void printHistory() {
System.out.println("\n=== 备忘录历史 ===");
for (int i = 0; i < mementoList.size(); i++) {
String marker = (i == currentIndex) ? "[当前] " : " ";
System.out.println(marker + "索引 " + i + ": " + mementoList.get(i));
}
System.out.println("==================\n");
}
// 获取备忘录数量
public int getMementoCount() {
return mementoList.size();
}
}
// 客户端
public class MementoPatternDemo {
public static void main(String[] args) {
// 创建原发器和管理者
Originator originator = new Originator();
Caretaker caretaker = new Caretaker();
// 设置初始状态
originator.setState("状态1");
caretaker.addMemento(originator.createMemento()); // 保存状态1
// 改变状态
originator.setState("状态2");
caretaker.addMemento(originator.createMemento()); // 保存状态2
// 改变状态
originator.setState("状态3");
caretaker.addMemento(originator.createMemento()); // 保存状态3
// 打印历史
caretaker.printHistory();
// 执行撤销操作
System.out.println("执行撤销操作:");
originator.restoreFromMemento(caretaker.undo()); // 恢复到状态2
System.out.println("当前状态: " + originator.getState());
// 再次执行撤销操作
System.out.println("再次执行撤销操作:");
originator.restoreFromMemento(caretaker.undo()); // 恢复到状态1
System.out.println("当前状态: " + originator.getState());
// 尝试再次撤销(应该失败)
System.out.println("尝试再次撤销:");
originator.restoreFromMemento(caretaker.undo());
System.out.println("当前状态: " + originator.getState());
// 执行重做操作
System.out.println("执行重做操作:");
originator.restoreFromMemento(caretaker.redo()); // 恢复到状态2
System.out.println("当前状态: " + originator.getState());
// 打印历史
caretaker.printHistory();
// 改变状态并保存(会清除重做历史)
originator.setState("状态4");
caretaker.addMemento(originator.createMemento()); // 保存状态4
System.out.println("当前状态: " + originator.getState());
// 打印历史
caretaker.printHistory();
// 尝试重做(应该失败,因为状态4是新保存的)
System.out.println("尝试重做:");
originator.restoreFromMemento(caretaker.redo());
}
}2. 复杂状态的备忘录实现
java
import java.io.*;
import java.util.*;
// 游戏角色状态类(复杂状态)
class GameCharacterState implements Serializable {
private String name;
private int health;
private int mana;
private int level;
private Map<String, Integer> inventory; // 物品栏
private String location;
private Date saveTime;
public GameCharacterState(String name, int health, int mana, int level, Map<String, Integer> inventory, String location) {
this.name = name;
this.health = health;
this.mana = mana;
this.level = level;
this.inventory = new HashMap<>(inventory); // 深拷贝物品栏
this.location = location;
this.saveTime = new Date();
}
// getter方法
public String getName() { return name; }
public int getHealth() { return health; }
public int getMana() { return mana; }
public int getLevel() { return level; }
public Map<String, Integer> getInventory() { return new HashMap<>(inventory); }
public String getLocation() { return location; }
public Date getSaveTime() { return saveTime; }
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append("角色状态[\n");
sb.append(" 名称: ").append(name).append("\n");
sb.append(" 等级: ").append(level).append("\n");
sb.append(" 生命值: ").append(health).append("\n");
sb.append(" 魔法值: ").append(mana).append("\n");
sb.append(" 位置: ").append(location).append("\n");
sb.append(" 物品栏: ").append(inventory).append("\n");
sb.append(" 保存时间: ").append(saveTime).append("\n");
sb.append("]");
return sb.toString();
}
}
// 游戏角色备忘录类
class GameMemento implements Serializable {
private GameCharacterState state;
private String saveName;
protected GameMemento(GameCharacterState state, String saveName) {
this.state = state;
this.saveName = saveName;
}
protected GameCharacterState getState() {
return state;
}
public String getSaveName() {
return saveName;
}
@Override
public String toString() {
return "存档['" + saveName + "'] - " + state.getSaveTime();
}
}
// 游戏角色类(原发器)
class GameCharacter {
private String name;
private int health;
private int mana;
private int level;
private Map<String, Integer> inventory;
private String location;
public GameCharacter(String name) {
this.name = name;
this.health = 100;
this.mana = 50;
this.level = 1;
this.inventory = new HashMap<>();
this.location = "新手村";
// 初始物品
inventory.put("木剑", 1);
inventory.put("皮甲", 1);
inventory.put("治疗药水", 3);
}
// 升级
public void levelUp() {
level++;
health = 100 + level * 10; // 生命值随等级增长
mana = 50 + level * 5; // 魔法值随等级增长
System.out.println(name + " 升级了!当前等级: " + level + ", 生命值: " + health + ", 魔法值: " + mana);
}
// 受伤
public void takeDamage(int damage) {
health = Math.max(0, health - damage);
System.out.println(name + " 受到了 " + damage + " 点伤害!当前生命值: " + health);
}
// 使用魔法
public void useMagic(int cost) {
if (mana >= cost) {
mana -= cost;
System.out.println(name + " 使用了魔法,消耗了 " + cost + " 点魔法值!当前魔法值: " + mana);
} else {
System.out.println(name + " 魔法值不足!");
}
}
// 获得物品
public void gainItem(String itemName, int quantity) {
inventory.put(itemName, inventory.getOrDefault(itemName, 0) + quantity);
System.out.println(name + " 获得了 " + quantity + " 个 " + itemName + "!");
}
// 移动到新位置
public void moveTo(String newLocation) {
this.location = newLocation;
System.out.println(name + " 移动到了 " + newLocation + "!");
}
// 创建备忘录
public GameMemento save(String saveName) {
System.out.println("保存游戏状态到存档: " + saveName);
return new GameMemento(
new GameCharacterState(name, health, mana, level, inventory, location),
saveName
);
}
// 从备忘录恢复
public void load(GameMemento memento) {
if (memento != null) {
GameCharacterState state = memento.getState();
this.name = state.getName();
this.health = state.getHealth();
this.mana = state.getMana();
this.level = state.getLevel();
this.inventory = state.getInventory();
this.location = state.getLocation();
System.out.println("从存档['" + memento.getSaveName() + "'] 加载游戏状态完成!");
}
}
// 显示当前状态
public void displayStatus() {
System.out.println("\n=== " + name + " 的当前状态 ===");
System.out.println("等级: " + level);
System.out.println("生命值: " + health);
System.out.println("魔法值: " + mana);
System.out.println("位置: " + location);
System.out.println("物品栏: " + inventory);
System.out.println("==============================\n");
}
}
// 游戏存档管理类(管理者)
class GameSaveManager {
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);
return memento;
} else {
System.out.println("未找到存档: " + saveName);
return null;
}
}
// 删除存档
public void deleteSave(String saveName) {
if (saves.remove(saveName) != null) {
System.out.println("存档已删除: " + saveName);
} else {
System.out.println("未找到存档: " + saveName);
}
}
// 列出所有存档
public void listSaves() {
System.out.println("\n=== 可用存档列表 ===");
if (saves.isEmpty()) {
System.out.println("没有可用的存档");
} else {
saves.forEach((name, memento) -> {
System.out.println("- " + memento);
});
}
System.out.println("====================\n");
}
// 获取存档数量
public int getSaveCount() {
return saves.size();
}
// 序列化存档到文件
public void saveToFile(String filename) throws IOException {
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filename))) {
oos.writeObject(saves);
System.out.println("所有存档已保存到文件: " + filename);
}
}
// 从文件加载存档
@SuppressWarnings("unchecked")
public void loadFromFile(String filename) throws IOException, ClassNotFoundException {
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename))) {
saves = (Map<String, GameMemento>) ois.readObject();
System.out.println("从文件加载存档完成: " + filename);
}
}
}
// 客户端:游戏示例
public class GameDemo {
public static void main(String[] args) {
// 创建游戏角色和存档管理器
GameCharacter hero = new GameCharacter("勇者");
GameSaveManager saveManager = new GameSaveManager();
// 显示初始状态
hero.displayStatus();
// 保存初始状态
saveManager.saveGame("save1", hero.save("初始状态"));
// 进行一些游戏操作
hero.levelUp();
hero.gainItem("铁剑", 1);
hero.moveTo("森林");
hero.takeDamage(30);
// 显示当前状态
hero.displayStatus();
// 保存当前状态
saveManager.saveGame("save2", hero.save("森林冒险"));
// 继续游戏操作
hero.levelUp();
hero.useMagic(20);
hero.gainItem("钻石", 5);
hero.moveTo("城堡");
// 显示当前状态
hero.displayStatus();
// 保存当前状态
saveManager.saveGame("save3", hero.save("城堡探索"));
// 列出所有存档
saveManager.listSaves();
// 从第一个存档加载
System.out.println("\n--- 从第一个存档加载 ---");
hero.load(saveManager.loadGame("save1"));
hero.displayStatus();
// 从第三个存档加载
System.out.println("\n--- 从第三个存档加载 ---");
hero.load(saveManager.loadGame("save3"));
hero.displayStatus();
// 删除第二个存档
System.out.println("\n--- 删除第二个存档 ---");
saveManager.deleteSave("save2");
saveManager.listSaves();
}
}实际应用示例:文本编辑器的撤销/重做功能
下面是一个实际应用的例子,展示如何使用备忘录模式实现文本编辑器的撤销/重做功能:
java
import java.util.ArrayList;
import java.util.List;
// 文档状态类(备忘录中存储的状态)
class DocumentState {
private String content; // 文档内容
private int cursorPosition; // 光标位置
private long timestamp; // 时间戳
public DocumentState(String content, int cursorPosition) {
this.content = content;
this.cursorPosition = cursorPosition;
this.timestamp = System.currentTimeMillis();
}
// getter方法
public String getContent() { return content; }
public int getCursorPosition() { return cursorPosition; }
public long getTimestamp() { return timestamp; }
@Override
public String toString() {
return "文档状态[内容长度=" + content.length() + ", 光标位置=" + cursorPosition + ", 时间戳=" + timestamp + "]";
}
}
// 文档备忘录类
class DocumentMemento {
private DocumentState state;
private String actionDescription; // 操作描述
protected DocumentMemento(DocumentState state, String actionDescription) {
this.state = state;
this.actionDescription = actionDescription;
}
protected DocumentState getState() {
return state;
}
public String getActionDescription() {
return actionDescription;
}
@Override
public String toString() {
return actionDescription + " - " + state;
}
}
// 文档类(原发器)
class Document {
private String content = ""; // 文档内容
private int cursorPosition = 0; // 光标位置
// 插入文本
public void insertText(String text) {
StringBuilder sb = new StringBuilder(content);
sb.insert(cursorPosition, text);
content = sb.toString();
cursorPosition += text.length();
System.out.println("插入文本: \"" + text + "\"");
System.out.println("当前内容: \"" + content + "\"");
System.out.println("光标位置: " + cursorPosition);
}
// 删除文本
public void deleteText(int length) {
if (length <= 0 || cursorPosition - length < 0) {
System.out.println("无法删除文本,参数无效");
return;
}
String deletedText = content.substring(cursorPosition - length, cursorPosition);
StringBuilder sb = new StringBuilder(content);
sb.delete(cursorPosition - length, cursorPosition);
content = sb.toString();
cursorPosition -= length;
System.out.println("删除文本: \"" + deletedText + "\"");
System.out.println("当前内容: \"" + content + "\"");
System.out.println("光标位置: " + cursorPosition);
}
// 设置光标位置
public void setCursorPosition(int position) {
if (position >= 0 && position <= content.length()) {
this.cursorPosition = position;
System.out.println("光标位置设置为: " + position);
} else {
System.out.println("无效的光标位置: " + position);
}
}
// 创建备忘录
public DocumentMemento save(String actionDescription) {
System.out.println("保存文档状态: " + actionDescription);
return new DocumentMemento(new DocumentState(content, cursorPosition), actionDescription);
}
// 从备忘录恢复
public void restore(DocumentMemento memento) {
if (memento != null) {
DocumentState state = memento.getState();
this.content = state.getContent();
this.cursorPosition = state.getCursorPosition();
System.out.println("恢复文档状态: " + memento.getActionDescription());
System.out.println("当前内容: \"" + content + "\"");
System.out.println("光标位置: " + cursorPosition);
}
}
// 显示当前状态
public void display() {
System.out.println("\n=== 文档当前状态 ===");
System.out.println("内容: \"" + content + "\"");
System.out.println("长度: " + content.length());
System.out.println("光标位置: " + cursorPosition);
// 显示光标位置指示器
StringBuilder indicator = new StringBuilder();
for (int i = 0; i < cursorPosition; i++) {
indicator.append(" ");
}
indicator.append("^");
System.out.println(indicator);
System.out.println("====================\n");
}
}
// 编辑器历史管理类(管理者)
class EditorHistory {
private List<DocumentMemento> history = new ArrayList<>(); // 历史记录
private int currentIndex = -1; // 当前索引
private int maxHistorySize = 100; // 最大历史记录数量
// 添加历史记录
public void addHistory(DocumentMemento memento) {
// 移除当前索引之后的所有历史记录
if (currentIndex < history.size() - 1) {
history = new ArrayList<>(history.subList(0, currentIndex + 1));
}
// 添加新的历史记录
history.add(memento);
currentIndex = history.size() - 1;
// 如果历史记录数量超过最大值,移除最旧的记录
if (history.size() > maxHistorySize) {
history.remove(0);
currentIndex--;
}
System.out.println("添加历史记录,当前索引: " + currentIndex);
}
// 撤销操作
public DocumentMemento undo() {
if (canUndo()) {
currentIndex--;
System.out.println("执行撤销操作,当前索引: " + currentIndex);
return history.get(currentIndex);
} else {
System.out.println("无法撤销,已经是最早的状态");
return null;
}
}
// 重做操作
public DocumentMemento redo() {
if (canRedo()) {
currentIndex++;
System.out.println("执行重做操作,当前索引: " + currentIndex);
return history.get(currentIndex);
} else {
System.out.println("无法重做,已经是最新的状态");
return null;
}
}
// 检查是否可以撤销
public boolean canUndo() {
return currentIndex > 0;
}
// 检查是否可以重做
public boolean canRedo() {
return currentIndex < history.size() - 1;
}
// 获取当前历史记录
public DocumentMemento getCurrentState() {
if (currentIndex >= 0 && currentIndex < history.size()) {
return history.get(currentIndex);
}
return null;
}
// 显示历史记录
public void showHistory() {
System.out.println("\n=== 历史记录 ===");
for (int i = 0; i < history.size(); i++) {
String marker = (i == currentIndex) ? "[当前] " : " ";
System.out.println(marker + "索引 " + i + ": " + history.get(i));
}
System.out.println("撤销: " + canUndo() + ", 重做: " + canRedo());
System.out.println("================\n");
}
// 获取历史记录数量
public int getHistorySize() {
return history.size();
}
// 清空历史记录
public void clearHistory() {
history.clear();
currentIndex = -1;
System.out.println("历史记录已清空");
}
}
// 文本编辑器类(客户端使用)
class TextEditor {
private Document document;
private EditorHistory history;
public TextEditor() {
this.document = new Document();
this.history = new EditorHistory();
// 保存初始状态
history.addHistory(document.save("初始状态"));
}
// 执行命令并保存历史
public void executeCommand(String command, Object... params) {
// 保存操作前的状态
history.addHistory(document.save("操作前: " + command));
// 执行命令
switch (command) {
case "insert":
if (params.length > 0 && params[0] instanceof String) {
document.insertText((String) params[0]);
}
break;
case "delete":
if (params.length > 0 && params[0] instanceof Integer) {
document.deleteText((Integer) params[0]);
}
break;
case "cursor":
if (params.length > 0 && params[0] instanceof Integer) {
document.setCursorPosition((Integer) params[0]);
}
break;
default:
System.out.println("未知命令: " + command);
break;
}
}
// 撤销
public void undo() {
DocumentMemento memento = history.undo();
if (memento != null) {
document.restore(memento);
}
}
// 重做
public void redo() {
DocumentMemento memento = history.redo();
if (memento != null) {
document.restore(memento);
}
}
// 显示文档
public void displayDocument() {
document.display();
}
// 显示历史
public void displayHistory() {
history.showHistory();
}
}
// 客户端:文本编辑器演示
public class TextEditorDemo {
public static void main(String[] args) {
// 创建文本编辑器
TextEditor editor = new TextEditor();
// 显示初始状态
editor.displayDocument();
// 执行一些编辑操作
editor.executeCommand("insert", "Hello");
editor.executeCommand("insert", " World");
editor.displayDocument();
editor.executeCommand("cursor", 5);
editor.executeCommand("insert", ",");
editor.displayDocument();
editor.executeCommand("cursor", 13);
editor.executeCommand("insert", "!");
editor.displayDocument();
// 显示历史
editor.displayHistory();
// 执行撤销操作
System.out.println("\n--- 执行撤销操作 ---");
editor.undo(); // 撤销插入感叹号
editor.displayDocument();
editor.undo(); // 撤销移动光标
editor.displayDocument();
// 显示历史
editor.displayHistory();
// 执行重做操作
System.out.println("\n--- 执行重做操作 ---");
editor.redo(); // 重做移动光标
editor.displayDocument();
editor.redo(); // 重做插入感叹号
editor.displayDocument();
// 执行删除操作
System.out.println("\n--- 执行删除操作 ---");
editor.executeCommand("cursor", 12);
editor.executeCommand("delete", 6); // 删除"World"
editor.displayDocument();
// 显示历史
editor.displayHistory();
// 执行撤销删除
System.out.println("\n--- 撤销删除操作 ---");
editor.undo(); // 撤销删除操作
editor.displayDocument();
// 最终历史
editor.displayHistory();
}
}优缺点
优点
- 状态封装:备忘录对象封装了原发器的内部状态,保持了良好的封装性
- 状态恢复:允许对象恢复到之前的状态,实现撤销/重做功能
- 简化原发器:原发器不需要管理状态历史,只需要创建和使用备忘录对象
- 状态隔离:备忘录对象的内容只能由原发器访问和修改,其他对象无法修改
缺点
- 内存消耗:如果需要保存大量的历史状态,可能会占用较多的内存
- 实现复杂:对于复杂对象的状态保存,实现起来可能比较复杂
- 版本兼容性:如果对象的结构发生变化,之前保存的备忘录可能无法正常使用
与其他模式的关系
- 备忘录模式与命令模式:都支持撤销操作,但命令模式关注的是操作的撤销,备忘录模式关注的是状态的恢复
- 备忘录模式与原型模式:都涉及到对象状态的复制,但原型模式关注的是创建对象的副本,备忘录模式关注的是保存和恢复对象的状态
- 备忘录模式与单例模式:在某些情况下,管理者可以设计为单例模式,以便全局访问历史状态
总结
备忘录模式是一种有用的行为型设计模式,它允许在不破坏封装性的前提下保存和恢复对象的内部状态。备忘录模式特别适用于需要实现撤销/重做功能、状态快照、历史记录等场景,如文本编辑器、图形编辑软件、游戏存档等。在使用备忘录模式时,需要注意管理备忘录对象的数量和内存消耗,避免因为保存过多的历史状态而导致性能问题。