👋hi,我不是一名外包公司的员工,也不会偷吃茶水间的零食,我的梦想是能写高端CRUD
🔥 2025本人正在沉淀中… 博客更新速度++
👍 欢迎点赞、收藏、关注,跟上我的更新节奏
🎵 当你的天空突然下了大雨,那是我在为你炸乌云
一、入门
什么是备忘录模式?
备忘录模式(Memento Pattern)是一种行为设计模式,用于在不破坏封装性的前提下,捕获并外部化一个对象的内部状态,以便在需要时恢复该状态。它通常用于实现撤销操作或保存对象的历史状态。
为什么需要备忘录模式?
假设我们正在开发一个简单的文本编辑器,支持以下功能:
- 用户可以输入文本。
- 用户可以保存当前文本状态。
- 用户可以撤销操作,恢复到之前保存的状态。
我们实现的代码如下
// 文本编辑器类
class Editor {
private String content;
public void setContent(String content) {
this.content = content;
}
public String getContent() {
return content;
}
// 保存状态(直接返回内部状态)
public String save() {
return content;
}
// 恢复状态(直接设置内部状态)
public void restore(String savedContent) {
this.content = savedContent;
}
}
客户端测试
// 客户端代码
public class TextEditorWithoutMemento {
public static void main(String[] args) {
Editor editor = new Editor();
// 用户输入文本
editor.setContent("First draft");
System.out.println("Current Content: " + editor.getContent());
// 保存状态
String savedState = editor.save();
// 用户修改文本
editor.setContent("Second draft");
System.out.println("Updated Content: " + editor.getContent());
// 撤销操作,恢复到之前的状态
editor.restore(savedState);
System.out.println("Restored Content: " + editor.getContent());
}
}
存在的问题
- 破坏封装性:
Editor
的content
状态通过save()
和restore()
方法直接暴露给外部,外部代码可以随意修改状态。 - 耦合性高:撤销逻辑直接依赖
Editor
的内部状态,如果Editor
的状态结构发生变化,撤销逻辑也需要修改。 - 难以扩展:如果需要支持多次撤销(保存多个历史状态),代码会变得复杂且难以维护。
如何实现备忘录模式?
备忘录模式的核心角色
- Originator(发起人):
- 负责创建备忘录,记录当前时刻的内部状态。
- 可以使用备忘录恢复内部状态。
- Memento(备忘录):
- 存储发起人的内部状态。
- 通常只允许发起人访问其内部状态,以防止其他对象修改。
- Caretaker(管理者):
- 负责保存备忘录,但不能修改或检查其内容。
- 提供保存和恢复备忘录的功能。
【案例】文本编辑 - 改
Memento(备忘录):EditorMemento
类,保存Editor
的状态
class EditorMemento {
private final String content;
public EditorMemento(String content) {
this.content = content;
}
public String getContent() {
return content;
}
}
Originator(发起人):Editor
类
class Editor {
private String content;
public void setContent(String content) {
this.content = content;
}
public String getContent() {
return content;
}
// 创建备忘录
public EditorMemento save() {
return new EditorMemento(content);
}
// 从备忘录恢复状态
public void restore(EditorMemento memento) {
this.content = memento.getContent();
}
}
Caretaker(管理者):History
类
class History {
private final List<EditorMemento> mementos = new ArrayList<>();
public void push(EditorMemento memento) {
mementos.add(memento);
}
public EditorMemento pop() {
if (mementos.isEmpty()) {
return null;
}
return mementos.remove(mementos.size() - 1);
}
}
客户端代码
public class TextEditorWithMemento {
public static void main(String[] args) {
Editor editor = new Editor();
History history = new History();
// 用户输入文本
editor.setContent("First draft");
history.push(editor.save()); // 保存状态
System.out.println("Current Content: " + editor.getContent());
// 用户修改文本
editor.setContent("Second draft");
history.push(editor.save()); // 保存状态
System.out.println("Updated Content: " + editor.getContent());
// 用户再次修改文本
editor.setContent("Third draft");
System.out.println("Updated Content: " + editor.getContent());
// 撤销操作
editor.restore(history.pop()); // 恢复到第二次保存的状态
System.out.println("Restored Content: " + editor.getContent());
// 再次撤销操作
editor.restore(history.pop()); // 恢复到第一次保存的状态
System.out.println("Restored Content: " + editor.getContent());
}
}
二、备忘录模式在框架源码中的运用
Spring 框架中的事务管理
Spring 框架中的事务管理也使用了备忘录模式的思想。在事务回滚时,Spring 需要恢复对象的状态。
备忘录(Memento):TransactionStatus
,TransactionStatus
保存了事务的当前状态
以下是精简版代码的
public interface TransactionStatus extends SavepointManager, Flushable {
boolean isNewTransaction(); // 是否是新事务
boolean hasSavepoint(); // 是否有保存点
void setRollbackOnly(); // 标记事务为回滚
boolean isRollbackOnly(); // 是否需要回滚
boolean isCompleted(); // 事务是否已完成
}
管理者(Caretaker):PlatformTransactionManager
PlatformTransactionManager
是 Spring 事务管理的核心接口,负责管理事务的生命周期。它通过 getTransaction()
方法获取事务状态(TransactionStatus
),并在事务提交或回滚时恢复状态。
public interface PlatformTransactionManager {
TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionStatus status) throws TransactionException;
}
发起人(Originator):TransactionTemplate
是Spring
提供的一个工具类,用于简化事务的编程式管理。它封装了事务的执行逻辑,并在事务执行过程中创建和恢复事务状态。
public class TransactionTemplate extends DefaultTransactionDefinition implements TransactionOperations {
private PlatformTransactionManager transactionManager;
@Override
public <T> T execute(TransactionCallback<T> action) throws TransactionException {
// 获取事务状态
TransactionStatus status = this.transactionManager.getTransaction(this);
T result;
try {
// 执行业务逻辑
result = action.doInTransaction(status);
} catch (RuntimeException | Error ex) {
// 回滚事务
rollbackOnException(status, ex);
throw ex;
}
// 提交事务
this.transactionManager.commit(status);
return result;
}
private void rollbackOnException(TransactionStatus status, Throwable ex) throws TransactionException {
if (status.isNewTransaction()) {
this.transactionManager.rollback(status);
}
}
}
三、总结
备忘录模式的优点
- 封装性好:
- 备忘录模式将对象的状态保存在一个独立的
Memento
对象中,避免了直接暴露对象的内部状态,保持了对象的封装性。
- 备忘录模式将对象的状态保存在一个独立的
- 简化状态管理:
- 通过将状态管理的逻辑集中到
Caretaker
中,避免了状态管理代码分散在业务逻辑中,使代码更清晰、更易维护。
- 通过将状态管理的逻辑集中到
- 支持撤销/重做功能:
- 备忘录模式天然适合实现撤销和重做功能,通过保存多个 Memento 对象,可以轻松恢复到之前的状态。
- 扩展性强:
- 如果需要支持多次撤销或保存多个历史状态,只需在
Caretaker
中维护多个Memento
对象即可。
- 如果需要支持多次撤销或保存多个历史状态,只需在
- 解耦:
- 备忘录模式将状态保存和恢复的逻辑与业务逻辑解耦,使得代码更易于复用和扩展。
备忘录模式的缺点
- 内存消耗:
- 如果需要保存大量的状态或状态数据较大,备忘录模式可能会占用较多的内存。
- 性能开销:
- 频繁地创建和恢复
Memento
对象可能会带来一定的性能开销,尤其是在状态数据较大的情况下。
- 频繁地创建和恢复
- 复杂性增加:
- 对于简单的场景,引入备忘录模式可能会增加代码的复杂性,显得“杀鸡用牛刀”。
- 状态暴露风险:
- 如果
Memento
对象的设计不当,可能会导致状态暴露给外部代码,破坏封装性。
- 如果
备忘录模式的适用场景
- 撤销/重做功能:
- 需要实现撤销和重做功能的场景,如文本编辑器、绘图工具等。
- 事务管理:
- 需要保存和恢复对象状态的场景,如数据库事务管理、游戏存档等。
- 历史记录:
- 需要保存对象的历史状态以供后续使用的场景,如操作日志、版本控制等。
- 状态快照:
- 需要在某个时间点保存对象的状态,并在后续恢复的场景,如游戏中的保存点、虚拟机快照等。
- 复杂对象的状态管理:
- 对于状态结构复杂的对象,备忘录模式可以简化状态管理逻辑。