备忘录模式 (Memento Pattern)
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态
模式定义
备忘录模式是一种行为型设计模式,它在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可将该对象恢复到原先保存的状态。备忘录模式又叫做快照模式(Snapshot Pattern)或Token模式,属于对象行为型模式。
模式结构
graph TD
A[Originator] --> B[Memento]
C[Caretaker] --> B
实现方式
基础实现
定义备忘录类、发起人类和管理者类。
// 备忘录类
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 saveStateToMemento() {
return new Memento(state);
}
// 从备忘录恢复状态
public void getStateFromMemento(Memento memento) {
state = memento.getState();
}
}
// 管理者类
class CareTaker {
private List mementoList = new ArrayList<>();
public void add(Memento state) {
mementoList.add(state);
}
public Memento get(int index) {
return mementoList.get(index);
}
public int size() {
return mementoList.size();
}
}
使用示例
public class MementoPatternDemo {
public static void main(String[] args) {
Originator originator = new Originator();
CareTaker careTaker = new CareTaker();
// 设置状态并保存
originator.setState("状态 #1");
careTaker.add(originator.saveStateToMemento());
System.out.println("当前状态: " + originator.getState());
originator.setState("状态 #2");
careTaker.add(originator.saveStateToMemento());
System.out.println("当前状态: " + originator.getState());
originator.setState("状态 #3");
careTaker.add(originator.saveStateToMemento());
System.out.println("当前状态: " + originator.getState());
// 恢复到之前的状态
System.out.println("\n=== 恢复状态 ===");
originator.getStateFromMemento(careTaker.get(1));
System.out.println("恢复到状态 #2: " + originator.getState());
originator.getStateFromMemento(careTaker.get(0));
System.out.println("恢复到状态 #1: " + originator.getState());
}
}
经典案例
1. 文本编辑器的撤销功能
文本编辑器中的撤销功能是备忘录模式的经典应用:
// 文本编辑器状态备忘录
class TextMemento {
private String text;
private int cursorPosition;
private LocalDateTime timestamp;
public TextMemento(String text, int cursorPosition) {
this.text = text;
this.cursorPosition = cursorPosition;
this.timestamp = LocalDateTime.now();
}
public String getText() {
return text;
}
public int getCursorPosition() {
return cursorPosition;
}
public LocalDateTime getTimestamp() {
return timestamp;
}
@Override
public String toString() {
return "状态 [时间=" + timestamp + ", 文本长度=" + text.length() + "]";
}
}
// 文本编辑器(发起人)
class TextEditor {
private String text = "";
private int cursorPosition = 0;
public void setText(String text) {
this.text = text;
}
public String getText() {
return text;
}
public void setCursorPosition(int cursorPosition) {
this.cursorPosition = cursorPosition;
}
public int getCursorPosition() {
return cursorPosition;
}
// 创建备忘录
public TextMemento save() {
System.out.println("保存状态: " + text.substring(0, Math.min(20, text.length())) + "...");
return new TextMemento(text, cursorPosition);
}
// 恢复状态
public void restore(TextMemento memento) {
this.text = memento.getText();
this.cursorPosition = memento.getCursorPosition();
System.out.println("恢复状态: " + text.substring(0, Math.min(20, text.length())) + "...");
}
// 模拟编辑操作
public void type(String newText) {
this.text += newText;
this.cursorPosition = this.text.length();
}
public void delete(int count) {
if (count <= text.length()) {
this.text = text.substring(0, text.length() - count);
this.cursorPosition = this.text.length();
}
}
}
// 历史记录管理器(管理者)
class HistoryManager {
private Stack history = new Stack<>();
private int maxSize;
public HistoryManager(int maxSize) {
this.maxSize = maxSize;
}
public void save(TextMemento memento) {
if (history.size() >= maxSize) {
history.remove(0); // 移除最旧的状态
}
history.push(memento);
}
public TextMemento undo() {
if (!history.isEmpty()) {
return history.pop();
}
return null;
}
public boolean canUndo() {
return !history.isEmpty();
}
public void showHistory() {
System.out.println("=== 历史记录 ===");
for (int i = 0; i < history.size(); i++) {
System.out.println((i + 1) + ". " + history.get(i));
}
}
}
// 使用示例
public class TextEditorDemo {
public static void main(String[] args) {
TextEditor editor = new TextEditor();
HistoryManager history = new HistoryManager(5);
// 编辑文本
editor.type("Hello");
history.save(editor.save());
editor.type(" World");
history.save(editor.save());
editor.type("!");
history.save(editor.save());
System.out.println("当前文本: " + editor.getText());
history.showHistory();
// 撤销操作
System.out.println("\n=== 执行撤销 ===");
if (history.canUndo()) {
editor.restore(history.undo());
System.out.println("撤销后文本: " + editor.getText());
}
if (history.canUndo()) {
editor.restore(history.undo());
System.out.println("再次撤销后文本: " + editor.getText());
}
}
}
2. 游戏存档系统
游戏中的存档和读档功能也是备忘录模式的应用:
// 游戏状态备忘录
class GameStateMemento {
private int level;
private int score;
private int health;
private List inventory;
private LocalDateTime saveTime;
public GameStateMemento(int level, int score, int health, List inventory) {
this.level = level;
this.score = score;
this.health = health;
this.inventory = new ArrayList<>(inventory);
this.saveTime = LocalDateTime.now();
}
// Getters
public int getLevel() { return level; }
public int getScore() { return score; }
public int getHealth() { return health; }
public List getInventory() { return new ArrayList<>(inventory); }
public LocalDateTime getSaveTime() { return saveTime; }
@Override
public String toString() {
return String.format("存档 [时间=%s, 关卡=%d, 分数=%d, 生命=%d, 物品数=%d]",
saveTime, level, score, health, inventory.size());
}
}
// 游戏角色(发起人)
class GameCharacter {
private int level = 1;
private int score = 0;
private int health = 100;
private List inventory = new ArrayList<>();
public void playLevel(int level) {
this.level = level;
this.score += level * 100;
System.out.println("完成第 " + level + " 关,获得 " + (level * 100) + " 分");
}
public void takeDamage(int damage) {
this.health -= damage;
if (this.health < 0) this.health = 0;
System.out.println("受到 " + damage + " 点伤害,剩余生命: " + this.health);
}
public void heal(int amount) {
this.health += amount;
if (this.health > 100) this.health = 100;
System.out.println("恢复 " + amount + " 点生命,当前生命: " + this.health);
}
public void addItem(String item) {
inventory.add(item);
System.out.println("获得物品: " + item);
}
// 创建存档
public GameStateMemento saveGame() {
System.out.println("游戏存档...");
return new GameStateMemento(level, score, health, inventory);
}
// 读取存档
public void loadGame(GameStateMemento memento) {
this.level = memento.getLevel();
this.score = memento.getScore();
this.health = memento.getHealth();
this.inventory = memento.getInventory();
System.out.println("读取存档: " + memento);
}
public void showStatus() {
System.out.println("=== 当前状态 ===");
System.out.println("关卡: " + level);
System.out.println("分数: " + score);
System.out.println("生命: " + health);
System.out.println("物品: " + inventory);
}
}
// 存档管理器(管理者)
class SaveGameManager {
private List saves = new ArrayList<>();
private int maxSaves = 3;
public void saveGame(GameStateMemento save) {
if (saves.size() >= maxSaves) {
saves.remove(0); // 删除最旧的存档
}
saves.add(save);
System.out.println("存档成功: " + save);
}
public GameStateMemento loadGame(int index) {
if (index >= 0 && index < saves.size()) {
return saves.get(index);
}
return null;
}
public void showSaves() {
System.out.println("=== 存档列表 ===");
for (int i = 0; i < saves.size(); i++) {
System.out.println((i + 1) + ". " + saves.get(i));
}
}
public int getSaveCount() {
return saves.size();
}
}
// 使用示例
public class GameDemo {
public static void main(String[] args) {
GameCharacter player = new GameCharacter();
SaveGameManager saveManager = new SaveGameManager();
// 游戏进程
player.showStatus();
player.playLevel(1);
player.addItem("剑");
saveManager.saveGame(player.saveGame());
player.playLevel(2);
player.takeDamage(30);
player.addItem("药水");
saveManager.saveGame(player.saveGame());
player.playLevel(3);
player.heal(50);
player.addItem("盾牌");
saveManager.saveGame(player.saveGame());
player.showStatus();
saveManager.showSaves();
// 读取存档
System.out.println("\n=== 读取第一个存档 ===");
GameStateMemento save = saveManager.loadGame(0);
if (save != null) {
player.loadGame(save);
player.showStatus();
}
}
}
应用场景
- 需要保存一个对象在某一个时刻的状态,并在适当的时候恢复到该状态
- 如果用一个接口来让其他对象直接得到这些状态,将会暴露对象的实现细节并破坏对象的封装性
- 文本编辑器的撤销操作
- 游戏的存档和读档功能
- 数据库事务回滚机制
- 浏览器的后退功能
- IDE的撤销/重做功能
优缺点
优点
- 保护封装性:备忘录模式将对象的内部状态保存在备忘录对象中,避免暴露对象的内部实现
- 简化发起人类:发起人不需要管理和保存其内部状态的各个版本
- 状态恢复机制:提供了一种状态恢复的机制,可以方便地恢复到历史状态
- 符合单一职责原则:发起人负责状态的创建和恢复,管理者负责状态的保存
缺点
- 消耗资源:如果需要保存的内部状态信息过多或者保存频率过高,会消耗大量内存
- 可能破坏封装性:为了保存和恢复对象状态,需要暴露一些内部状态给其他对象
- 增加系统复杂性:引入备忘录模式会增加系统的复杂性