文章目录
- [0. 个人感悟](#0. 个人感悟)
- [1. 概念](#1. 概念)
- [2. 适配场景](#2. 适配场景)
-
- [2.1 适合的场景](#2.1 适合的场景)
- [2.2 常见场景举例](#2.2 常见场景举例)
- [3. 实现方法](#3. 实现方法)
-
- [3.1 实现思路](#3.1 实现思路)
- [3.2 UML类图](#3.2 UML类图)
- [3.3 代码示例](#3.3 代码示例)
- [4. 优缺点](#4. 优缺点)
-
- [4.1 优点](#4.1 优点)
- [4.2 缺点](#4.2 缺点)
- [5. 源码分析](#5. 源码分析)
-
- [5.1 Java Swing中的UndoManager](#5.1 Java Swing中的UndoManager)
0. 个人感悟
- 备忘录模式的场景也比较专。适合进行备份、恢复
- 模式优点很明显,状态保存于业务逻辑分离,实现解耦
- 一个注意的点是窄接口设计,Memento(备忘录)内部保存了Originator(原发器)的状态,但这个状态不应该被除了Originator之外的其他对象(特别是Caretaker)直接访问或修改,以保护状态的封装性。具体可以看看示例代码
1. 概念
英文定义(《设计模式:可复用面向对象软件的基础》)
Without violating encapsulation, capture and externalize an object,t internal state so that the object can be restored this state later.
中文翻译
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,以便以后可以将对象恢复到原先保存的状态。
理解
- 备忘录模式提供了一种状态恢复机制,允许对象回到某个历史状态
- 它通过独立的对象(备忘录)来存储状态,而不是由原对象自己保存
- 实现了状态保存与业务逻辑的分离,原对象专注于业务,管理者专注于状态管理
2. 适配场景
2.1 适合的场景
- 需要撤销/重做功能:如文本编辑器、绘图软件的操作历史
- 需要保存快照/检查点:如游戏存档、系统配置备份
- 状态转换复杂:如工作流引擎的状态管理
- 需要事务回滚:如数据库操作的事务管理
- 原型状态保存:如复杂对象构造过程中的中间状态保存
- 需要版本控制:如文档编辑的历史版本管理
2.2 常见场景举例
- IDE开发环境:Eclipse/IntelliJ IDEA的代码编辑撤销功能
- 绘图软件:Photoshop的历史记录面板
- 游戏开发:游戏进度存档/读档系统
- 表单应用:多步表单的回退功能
- 配置管理:系统配置的备份与恢复
- 数据库系统:事务的rollback机制
3. 实现方法
3.1 实现思路
- 识别状态数据:确定需要保存的内部状态字段
- 创建Memento类:设计存储状态的数据结构
- 在Originator中添加状态管理 :
- 创建保存状态的方法(
saveToMemento()) - 创建恢复状态的方法(
restoreFromMemento())
- 创建保存状态的方法(
- 设计Caretaker类 :
- 决定存储策略(栈、列表、树等)
- 实现状态管理逻辑(撤销、重做、清空等)
- 控制访问权限 :
- 对Originator开放宽接口(可读写状态)
- 对Caretaker开放窄接口(只读元数据)
3.2 UML类图

角色说明:
- Originator(原发器):需要保存状态的对象,负责创建和恢复备忘录
- Memento(备忘录):存储Originator内部状态,通常设计为不可变对象
- Caretaker(管理者):负责保存和管理备忘录,但不能操作备忘录内容
3.3 代码示例
背景
以文件编辑器为例,简化业务,只有保存和撤销功能
设计

原发器
java
public class TextEditor {
private String content;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public TextEditor(String content) {
this.content = content;
}
/**
* @description 创建备忘
* @author bigHao
* @date 2026/1/27
* @return designpattern.memento.pac.TextMemento 备忘录
**/
public TextMemento createMemento() {
return new TextMemento(content);
}
/**
* @description 恢复备忘
* @author bigHao
* @date 2026/1/27
* @param memento 备忘录
**/
public void restoreMemento(TextMemento memento) {
this.content = memento.getContent();
}
}
备忘录
- 注意权限控制。备忘录只有原发器可以创建和访问内容。实际操作中将二者放到一个包下,相关方法使用包权限
java
public class TextMemento {
private String content;
// 包访问权限,仅对Originator开放状态访问
TextMemento(String content) {
this.content = content;
}
String getContent() {
return content;
}
}
管理者
java
public class Caretaker {
private static final int MAX_SIZE = 100;
// 业务场景刚好使用stack 先进后出
private Stack<TextMemento> history = new Stack<>();
public void push(TextMemento memento) {
// 限制下长度
limitHistory();
this.history.push(memento);
}
public TextMemento pop() {
return this.history.pop();
}
private void limitHistory() {
if (history.size() > MAX_SIZE) {
history.removeFirst();
}
}
}
测试
java
public class Client {
static void main() {
// 创建
String content = "备忘录模式笔记";
TextEditor textEditor = new TextEditor(content);
printInfo(textEditor);
Caretaker caretaker = new Caretaker();
// 安全考虑,TextMemento是包访问权限,和TextEditor同包,其它地方无法直接创建
// new TextMemento(""); 会报错
// 保存1
caretaker.push(textEditor.createMemento());
content += "包括三个角色,分别是原发器";
textEditor.setContent(content);
caretaker.push(textEditor.createMemento());
printInfo(textEditor);
// 保存2
content += "、备忘录";
textEditor.setContent(content);
caretaker.push(textEditor.createMemento());
printInfo(textEditor);
// 保存3
content += "和管理者";
textEditor.setContent(content);
caretaker.push(textEditor.createMemento());
printInfo(textEditor);
// 编辑
content += " 功能分别是";
textEditor.setContent(content);
printInfo(textEditor);
// 撤销1
System.out.println("===撤销===");
textEditor.restoreMemento(caretaker.pop());
printInfo(textEditor);
// 撤销2
System.out.println("===撤销===");
textEditor.restoreMemento(caretaker.pop());
printInfo(textEditor);
}
private static void printInfo(TextEditor textEditor) {
System.out.println("当前内容: " + textEditor.getContent());
}
}
输出
当前内容: 备忘录模式笔记
当前内容: 备忘录模式笔记包括三个角色,分别是原发器
当前内容: 备忘录模式笔记包括三个角色,分别是原发器、备忘录
当前内容: 备忘录模式笔记包括三个角色,分别是原发器、备忘录和管理者
当前内容: 备忘录模式笔记包括三个角色,分别是原发器、备忘录和管理者 功能分别是
===撤销===
当前内容: 备忘录模式笔记包括三个角色,分别是原发器、备忘录和管理者
===撤销===
当前内容: 备忘录模式笔记包括三个角色,分别是原发器、备忘录
4. 优缺点
4.1 优点
高内聚低耦合
- 职责分离:状态保存与业务逻辑解耦,Originator只关注核心功能
- 封装性:通过窄接口模式保护状态不被Caretaker误操作
复用性
- 状态管理可复用:Caretaker可被多个Originator复用
- 存储策略灵活:支持栈、列表、数据库等多种存储方式
可读性
- 意图明确:代码明确表达了"保存状态"和"恢复状态"的意图
- 结构清晰:三个角色分工明确,易于理解
维护性
- 易于扩展:可轻松添加新的存储策略或状态类型
- 修改隔离:状态存储逻辑变化不影响Originator
稳定性
- 状态一致:确保状态恢复的正确性
- 异常安全:状态操作失败时可恢复到之前状态
4.2 缺点
资源消耗
- 内存占用:大量状态保存可能导致内存消耗过大
- 性能开销:频繁的状态保存/恢复可能影响性能
设计复杂度
- 额外类:增加了Memento和Caretaker类
- 窄接口实现复杂:需要小心设计访问权限
状态同步
- 深拷贝问题:复杂对象的深拷贝可能实现复杂
- 版本兼容:状态数据结构变化时,旧备忘录可能无法恢复
5. 源码分析
5.1 Java Swing中的UndoManager
代码位置 :javax.swing.undo.UndoManager
角色分析:
java
// Originator:各种Document类(如PlainDocument)
public class PlainDocument extends AbstractDocument {
// 创建UndoableEdit(备忘录)
protected UndoableEdit createUndoableEdit(ElementChange ec) {
return new DocumentUndoableEdit(ec);
}
}
// Memento:UndoableEdit接口及其实现
public interface UndoableEdit {
void undo() throws CannotUndoException;
void redo() throws CannotRedoException;
boolean canUndo();
boolean canRedo();
}
// Caretaker:UndoManager类
public class UndoManager extends CompoundEdit implements UndoableEditListener {
private Vector<UndoableEdit> edits; // 存储备忘录
private int indexOfNextAdd; // 下一个添加位置
public void undo() throws CannotUndoException {
// 执行撤销操作
UndoableEdit edit = edits.elementAt(--indexOfNextAdd);
edit.undo();
}
public void redo() throws CannotRedoException {
// 执行重做操作
UndoableEdit edit = edits.elementAt(indexOfNextAdd);
edit.redo();
indexOfNextAdd++;
}
}
参考: