设计模式之管理状态_Memento备忘录模式_保存对象状态
前言
Github:https://github.com/HealerJean
一、备忘录模式(Memento Pattern)
1. 模式概述
备忘录模式 是一种 行为型设计模式,它在不破坏封装性的前提下,捕获并外部化一个对象的内部状态,以便以后可将该对象恢复到原先保存的状态。
核心思想:“时光倒流” —— 保存快照,随时回滚。
备忘录模式 = 安全快照 + 时光回溯
- 它不是“备份数据库”,而是“代码级状态保险”。
类比理解:
- 游戏存档:闯关前保存进度,失败后读档重来;
- VMware 快照:虚拟机状态冻结,可随时回退;
- Word 撤销(Undo):每一步操作生成快照,支持多级回退。
2. 使用场景
备忘录模式适用于以下情况:
- 需要 实现撤销(Undo)/重做(Redo) 功能;
- 对象状态 复杂且需完整保存,但又不能暴露内部细节;
- 需要 在不破坏封装性 的前提下保存/恢复状态;
- 工作流引擎中支持 回退到历史节点。
典型应用:
- 文本编辑器的撤销栈;
- 游戏存档系统;
- 事务回滚机制(轻量级);
- 表单草稿自动保存。
3. 示例程序:游戏角色状态管理(优化版)
1)备忘录 GameState
/**
* 备忘录(Memento)
* 仅 Originator 可读写,对外提供有限访问(可选)
*/
public final class GameState {
private final String level;
private final float x;
private final float y;
private final int health;
public GameState(String level, float x, float y, int health) {
this.level = level;
this.x = x;
this.y = y;
this.health = health;
}
// 仅用于 Originator 恢复(包级私有或通过内部类实现更佳)
String getLevel() { return level; }
float getX() { return x; }
float getY() { return y; }
int getHealth() { return health; }
@Override
public String toString() {
return String.format("Level=%s, Pos=(%.1f, %.1f), HP=%d", level, x, y, health);
}
}
2)发起人 GameCharacter
/**
* 发起人(Originator)
* 负责创建和恢复备忘录
*/
public class GameCharacter {
private String level = "1-1";
private float x = 0.0f;
private float y = 0.0f;
private int health = 100;
// 创建备忘录(保存当前状态)
public GameState saveState() {
return new GameState(level, x, y, health);
}
// 从备忘录恢复状态
public void restoreState(GameState state) {
this.level = state.getLevel();
this.x = state.getX();
this.y = state.getY();
this.health = state.getHealth();
}
// 模拟游戏行为
public void move(float dx, float dy) {
this.x += dx;
this.y += dy;
}
public void takeDamage(int damage) {
this.health = Math.max(0, this.health - damage);
}
@Override
public String toString() {
return String.format("当前状态: Level=%s, Pos=(%.1f, %.1f), HP=%d", level, x, y, health);
}
}
3)管理者 HistoryManager
import java.util.*;
/**
* 管理者(Caretaker)
* 仅负责存储和获取备忘录,不操作其内容
*/
public class HistoryManager {
private final Deque<GameState> history = new ArrayDeque<>();
private static final int MAX_HISTORY = 10; // 限制历史数量
public void save(GameState state) {
if (history.size() >= MAX_HISTORY) {
history.removeLast(); // 移除最旧记录
}
history.addFirst(state);
}
public GameState undo() {
if (history.isEmpty()) {
throw new IllegalStateException("无历史记录可恢复");
}
GameState current = history.removeFirst();
return history.isEmpty() ? null : history.peekFirst();
}
public List<GameState> getHistory() {
return new ArrayList<>(history);
}
}
4)客户端测试 Main
public class Main {
public static void main(String[] args) {
GameCharacter player = new GameCharacter();
HistoryManager history = new HistoryManager();
// 初始状态
history.save(player.saveState());
System.out.println(player);
// 行动1:移动
player.move(10, 5);
history.save(player.saveState());
System.out.println(player);
// 行动2:受伤
player.takeDamage(30);
history.save(player.saveState());
System.out.println(player);
// 撤销一次(回退到移动后、受伤前)
GameState prevState = history.undo();
if (prevState != null) {
player.restoreState(prevState);
}
System.out.println("\n【撤销后】" + player);
// 查看历史
System.out.println("\n历史记录:");
history.getHistory().forEach(System.out::println);
}
}
输出示例:
当前状态: Level=1-1, Pos=(0.0, 0.0), HP=100
当前状态: Level=1-1, Pos=(10.0, 5.0), HP=100
当前状态: Level=1-1, Pos=(10.0, 5.0), HP=70
【撤销后】当前状态: Level=1-1, Pos=(10.0, 5.0), HP=100
历史记录:
Level=1-1, Pos=(10.0, 5.0), HP=100
Level=1-1, Pos=(0.0, 0.0), HP=100
5)模式角色
- 封装性:Caretaker 只能传递 Memento,不能访问其内部数据;
- 快照隔离:每个 Memento 是独立状态副本,互不影响。
| 角色 | 职责 | 示例 |
|---|---|---|
| Originator(发起人) | 创建备忘录、从备忘录恢复自身状态 | GameCharacter |
| Memento(备忘录) | 存储 Originator 的内部状态(通常为不可变) | GameStateSnapshot |
| Caretaker(管理者) | 负责保存和管理 Memento,但不能修改或查看其内容 | HistoryManager |
4. FQA
1)备忘录模式 vs 原型模式
| 对比项 | 备忘录模式 | 原型模式 |
|---|---|---|
| 目的 | 保存/恢复对象部分或全部状态(用于回滚) | 复制对象(避免重复创建开销) |
| 是否生成新实例 | 恢复时修改原对象状态 | 返回全新克隆对象 |
| 封装性 | 高(Caretaker 无法访问内部状态) | 依赖 clone() 实现 |
| 典型用途 | Undo/Redo、快照 | 对象池、配置模板复制 |
2)模式优点
- 提供状态回滚能力,支持撤销/重做;
- 保持封装性:外部无法直接访问对象内部状态;
- 状态隔离:每个快照独立,互不影响。
3)注意事项
- 内存消耗大:保存大量快照可能占用过多内存;
- 性能开销:频繁保存/恢复影响效率;
- 不适用于大对象:若状态数据庞大,需考虑序列化或增量快照。


