2025/10/16日 每日总结 设计模式实践:备忘录模式实现多次撤销功能
设计模式实践:备忘录模式实现多次撤销功能
在日常软件使用中,“撤销”是高频需求——比如文档编辑、信息填写时,需要回溯到之前的操作状态。普通的单次撤销容易实现,但多次撤销需要妥善管理历史状态,避免状态混乱或数据泄露。备忘录模式通过分离“状态存储”与“业务逻辑”,完美解决这一问题,本文将以“用户信息编辑多次撤销”为例,分享备忘录模式的设计与实现。
一、备忘录模式核心思想
备忘录模式是一种行为型设计模式,核心目标是保存对象的历史状态,支持灵活回溯,其关键结构包括:
-
原发器(Originator):即状态的拥有者(如用户信息对象),负责创建备忘录(保存当前状态)和从备忘录恢复状态;
-
备忘录(Memento):封装原发器的状态,仅暴露状态的获取/设置接口,保护状态不被外部篡改;
-
管理者(Caretaker):负责管理备忘录集合,提供添加、查询、删除备忘录的功能,不直接操作状态数据。
本次实践中,用户信息(原发器)的每次修改都会生成备忘录并存储在管理者中,撤销时从管理者获取对应历史备忘录,恢复到指定状态,支持多次回溯。
二、类图设计
| Originator | // 原发器:用户信息
+-------------------+
- state: HashMap<String, String> // 存储用户信息(姓名、年龄等)
+-------------------+
- createMemento(): Memento // 创建备忘录(保存当前状态)
- restoreMemento(memento: Memento): void // 从备忘录恢复状态
- setState(state: HashMap): void // 设置状态
- getState(): HashMap // 获取状态
- setField(key: String, value: String): void // 修改单个字段
- display(): void // 显示当前状态
+-------------------+
+-------------------+
| Memento | // 备忘录:保存用户信息状态
+-------------------+
- state: HashMap<String, String> // 封装的状态数据
+-------------------+
- Memento(state: HashMap): void // 构造方法(深拷贝状态)
- getState(): HashMap // 获取状态(返回拷贝,保护原始数据)
- setState(state: HashMap): void // 设置状态(深拷贝)
+-------------------+
+-------------------+
| Caretaker | // 管理者:管理备忘录集合
+-------------------+
- mementoList: ArrayList<Memento> // 存储历史备忘录
- maxSize: int // 最大历史记录数(防止内存溢出)
+-------------------+
- addMemento(memento: Memento): void // 添加备忘录
- getMemento(index: int): Memento // 获取指定索引的备忘录
- getLastMemento(): Memento // 获取上一次状态的备忘录(撤销用)
- getHistorySize(): int // 获取历史记录数
- clearHistory(): void // 清空历史记录(保留当前状态)
+-------------------+
三、完整代码实现
1. 备忘录类(Memento)
封装用户信息状态,通过深拷贝确保历史状态不被当前操作篡改,仅提供安全的状态访问接口。
import java.util.ArrayList;
import java.util.HashMap;
/**
* 备忘录类:封装用户信息状态,保护状态安全性
*/
class Memento {
private HashMap<String, String> state;
// 构造方法:深拷贝传入的状态,避免外部引用影响
public Memento(HashMap<String, String> state) {
this.state = new HashMap<>(state);
}
// 获取状态:返回拷贝,防止外部修改内部状态
public HashMap<String, String> getState() {
return new HashMap<>(state);
}
// 设置状态:深拷贝传入的状态
public void setState(HashMap<String, String> state) {
this.state = new HashMap<>(state);
}
}
2. 原发器类(Originator)
用户信息的核心类,负责状态的修改、备忘录创建和状态恢复,不关心历史状态的管理逻辑。
/**
* 原发器类:用户信息,状态的拥有者
*/
class Originator {
private HashMap<String, String> state = new HashMap<>();
/**
* 创建备忘录:保存当前状态到备忘录
* @return 包含当前状态的备忘录
*/
public Memento createMemento() {
return new Memento(state);
}
/**
* 从备忘录恢复状态
* @param memento 待恢复的备忘录
*/
public void restoreMemento(Memento memento) {
this.state = memento.getState();
}
/**
* 设置完整状态
* @param state 新的用户信息状态
*/
public void setState(HashMap<String, String> state) {
this.state = new HashMap<>(state);
}
/**
* 获取当前状态(返回拷贝,保护内部状态)
* @return 当前用户信息状态
*/
public HashMap<String, String> getState() {
return new HashMap<>(state);
}
/**
* 修改单个字段(如姓名、年龄)
* @param key 字段名(如"name"、"age")
* @param value 字段值
*/
public void setField(String key, String value) {
state.put(key, value);
}
/**
* 显示当前用户信息
*/
public void display() {
System.out.println("当前用户信息: " + state);
}
}
3. 管理者类(Caretaker)
管理备忘录集合,支持添加、查询历史备忘录,设置最大记录数防止内存溢出,提供撤销所需的状态查询接口。
/**
* 管理者类:管理备忘录集合,提供多次撤销支持
*/
class Caretaker {
private ArrayList<Memento> mementoList = new ArrayList<>();
private int maxSize = 10; // 最大历史记录数,可根据需求调整
/**
* 添加备忘录到历史记录
* @param memento 待保存的备忘录
*/
public void addMemento(Memento memento) {
// 超过最大记录数时,移除最旧的记录
if (mementoList.size() >= maxSize) {
mementoList.remove(0);
}
mementoList.add(memento);
}
/**
* 获取指定索引的备忘录
* @param index 备忘录索引
* @return 对应的备忘录(索引无效返回null)
*/
public Memento getMemento(int index) {
if (index >= 0 && index < mementoList.size()) {
return mementoList.get(index);
}
return null;
}
/**
* 获取上一次状态的备忘录(用于撤销操作)
* @return 上一次状态的备忘录(无历史记录返回null)
*/
public Memento getLastMemento() {
// 保留当前状态,返回上一个状态(mementoList最后一个是当前状态)
if (mementoList.size() > 1) {
return mementoList.get(mementoList.size() - 2);
}
return null;
}
/**
* 获取历史记录数量
* @return 备忘录集合大小
*/
public int getHistorySize() {
return mementoList.size();
}
/**
* 清空历史记录(保留当前状态)
*/
public void clearHistory() {
if (!mementoList.isEmpty()) {
Memento current = mementoList.get(mementoList.size() - 1);
mementoList.clear();
mementoList.add(current);
}
}
}
4. 客户端测试类(MementoPatternDemo)
模拟用户信息的多次修改、撤销操作,验证备忘录模式的多次撤销功能。
/**
* 客户端测试:验证多次撤销功能
*/
public class MementoPatternDemo {
public static void main(String[] args) {
// 1. 创建原发器(用户信息)和管理者(备忘录管理)
Originator originator = new Originator();
Caretaker caretaker = new Caretaker();
// 2. 初始状态:设置初始用户信息并保存备忘录
HashMap<String, String> initialState = new HashMap<>();
initialState.put("name", "张三");
initialState.put("age", "25");
initialState.put("phone", "13800138000");
originator.setState(initialState);
caretaker.addMemento(originator.createMemento());
System.out.println("=== 初始状态 ===");
originator.display();
System.out.println("历史记录数: " + caretaker.getHistorySize());
// 3. 第一次修改:修改姓名和年龄,保存备忘录
System.out.println("\n=== 第一次修改 ===");
originator.setField("name", "李四");
originator.setField("age", "30");
caretaker.addMemento(originator.createMemento());
originator.display();
System.out.println("历史记录数: " + caretaker.getHistorySize());
// 4. 第二次修改:修改电话和新增邮箱,保存备忘录
System.out.println("\n=== 第二次修改 ===");
originator.setField("phone", "13900139000");
originator.setField("email", "lisi@example.com");
caretaker.addMemento(originator.createMemento());
originator.display();
System.out.println("历史记录数: " + caretaker.getHistorySize());
// 5. 第一次撤销:恢复到第二次修改前的状态
System.out.println("\n=== 第一次撤销 ===");
Memento memento = caretaker.getLastMemento();
if (memento != null) {
originator.restoreMemento(memento);
caretaker.addMemento(originator.createMemento()); // 保存撤销后的状态
}
originator.display();
System.out.println("历史记录数: " + caretaker.getHistorySize());
// 6. 第二次撤销:恢复到初始状态
System.out.println("\n=== 第二次撤销 ===");
memento = caretaker.getLastMemento();
if (memento != null) {
originator.restoreMemento(memento);
caretaker.addMemento(originator.createMemento());
}
originator.display();
System.out.println("历史记录数: " + caretaker.getHistorySize());
// 7. 查看所有历史记录
System.out.println("\n=== 所有历史记录 ===");
for (int i = 0; i < caretaker.getHistorySize(); i++) {
Memento histMemento = caretaker.getMemento(i);
System.out.println("记录 " + i + ": " + histMemento.getState());
}
}
}
四、运行结果与验证
=== 初始状态 ===
当前用户信息: {name=张三, age=25, phone=13800138000}
历史记录数: 1
=== 第一次修改 ===
当前用户信息: {name=李四, age=30, phone=13800138000}
历史记录数: 2
=== 第二次修改 ===
当前用户信息: {name=李四, age=30, phone=13900139000, email=lisi@example.com}
历史记录数: 3
=== 第一次撤销 ===
当前用户信息: {name=李四, age=30, phone=13800138000}
历史记录数: 4
=== 第二次撤销 ===
当前用户信息: {name=张三, age=25, phone=13800138000}
历史记录数: 5
=== 所有历史记录 ===
记录 0: {name=张三, age=25, phone=13800138000}
记录 1: {name=李四, age=30, phone=13800138000}
记录 2: {name=李四, age=30, phone=13900139000, email=lisi@example.com}
记录 3: {name=李四, age=30, phone=13800138000}
记录 4: {name=张三, age=25, phone=13800138000}
结果分析:
-
状态保存:每次修改后调用
addMemento,历史记录数递增,状态被安全存储; -
多次撤销:两次撤销分别恢复到“第二次修改前”和“初始状态”,状态回溯准确;
-
数据安全:备忘录通过深拷贝存储状态,修改当前状态不会影响历史记录;
-
边界控制:管理者设置最大记录数,避免无限制存储导致内存溢出。
五、备忘录模式的核心优势与适用场景
核心优势
-
状态与逻辑分离:原发器专注业务逻辑,管理者专注状态管理,符合“单一职责原则”;
-
状态安全:备忘录封装状态,仅暴露必要接口,防止外部篡改历史状态;
-
灵活回溯:支持任意次数撤销(受最大记录数限制),可回溯到任意历史状态;
-
扩展性强:新增状态字段或扩展撤销逻辑时,无需修改核心业务代码。
适用场景
-
需要多次撤销/恢复的场景:如文档编辑、表格填写、图形设计软件;
-
状态频繁变化且需回溯的场景:如配置中心的配置修改、用户信息编辑;
-
需保护状态隐私的场景:不希望外部直接访问对象内部状态,通过备忘录间接访问;
-
日志记录与恢复场景:如数据库事务回滚、操作日志回溯。
六、关键实现细节与优化
1. 深拷贝的重要性
如果使用浅拷贝,当前状态的修改会同步影响历史备忘录中的状态(因为共享引用),导致撤销功能失效。本文通过new HashMap<>(state)实现深拷贝,确保历史状态独立。
2. 历史记录数量控制
管理者设置maxSize,当历史记录超过阈值时移除最旧记录,避免内存溢出,尤其适用于高频修改场景。
3. 撤销后的状态保存
每次撤销后,将恢复后的状态重新添加到备忘录集合,支持“撤销后再修改”的场景,避免历史记录断裂。
七、实践总结
-
备忘录模式的核心是“分离状态存储与业务逻辑”,通过原发器、备忘录、管理者三者配合,实现安全灵活的状态回溯;
-
深拷贝是确保状态独立性的关键,浅拷贝会导致历史状态被污染,需重点注意;
-
管理者的设计决定了撤销功能的灵活性,可根据需求扩展“指定索引恢复”“重做”等功能;
-
备忘录模式适用于所有需要状态回溯的场景,是实现“撤销”功能的最优设计模式之一。
通过本次用户信息多次撤销的实践,我深刻体会到备忘录模式在状态管理中的价值。合理运用该模式,能让软件的撤销功能更稳定、安全,同时提升代码的可维护性和扩展性。

浙公网安备 33010602011771号