Java设计模式————备忘录模式讲解
备忘录模式的最初描述是:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可以将该对象恢复到原先保存的状态。
核心思想:提供一种状态撤销/恢复机制,同时保持对象内部的封装性,不让外部直接访问内部状态。
通俗的讲,就是提供提供撤销 / 回滚 / 存档恢复功能,但绝对不泄露对象的内部隐私数据,外部不能乱改内部属性。
备忘录模式包括三种角色:原发者(需要在某个时刻保存其状态的对象。原发者负责创建备忘录,然后原发者使用该备忘录记录自己的状态),备忘录(负责存储原发者状态的对象),负责人(负责管理保存备忘录对象)。
逻辑解析
public class Originator {
// 核心:需要保存/恢复的内部状态(私有)
private String state;
// 修改自身状态
public void setState(String state) {
this.state = state;
}
public String getState() {
return state;
}
// 关键方法1:创建备忘录 → 把自己当前状态打包,生成快照
public Memento createMemento() {
return new Memento(state);
}
// 关键方法2:恢复备忘录 → 拿着快照,把自己状态改回去
public void restoreMemento(Memento memento) {
this.state = memento.getState();
}
}
public class Memento {
// final 不可变:一旦存档,状态不能被篡改,安全
private final String state;
// 构造方法:只能由发起人传入状态
public Memento(String state) {
this.state = state;
}
// 只有包级私有getter,没有setter
// 外部类无法修改、随意获取内部状态
String getState() {
return state;
}
}
public class Caretaker {
// 集合:存放多条备忘录 = 多份历史存档
private final List<Memento> mementoList = new ArrayList<>();
// 新增一份存档
public void addMemento(Memento memento) {
mementoList.add(memento);
}
// 根据索引,取出指定历史存档
public Memento getMemento(int index) {
return mementoList.get(index);
}
}
下面我们写个简单的例子
1.Snapshot.java - 备忘录(Memento)
作用:存储 TextEditor 的内部状态快照
import java.util.Date;
// 快照(Memento)
class Snapshot {
private final String content;
private final Date timestamp;
public Snapshot(String content) {
this.content = content;
this.timestamp = new Date();
}
public String getContent() {
return content;
}
public Date getTimestamp() {
return timestamp;
}
}
第1行:导入 Date 类,用于记录快照创建的时间戳
第4行:定义 Snapshot 类,这是备忘录角色
第5行:private final String content - 用私有不可变字段存储文本内容
final 确保快照一旦创建就不能被修改(保证状态的稳定性)
第6行:private final Date timestamp - 记录快照的创建时间
第8-11行:构造函数
接收一个字符串参数作为内容
自动记录当前时间作为时间戳
第13-15行:getContent() 方法 - 提供只读访问内容
第17-19行:getTimestamp() 方法 - 提供只读访问时间戳
设计要点:
只提供 getter,没有 setter → 防止外部篡改快照
使用 final 修饰符 → 确保不可变性
2.TextEditor.java - 发起人(Originator)
作用:这是需要保存状态的核心对象,负责创建和恢复快照
// 文本编辑器(Originator)
class TextEditor {
private StringBuilder content = new StringBuilder();
public void write(String text) {
content.append(text);
}
public void deleteLast(int n) {
if (n <= content.length()) {
content.delete(content.length() - n, content.length());
}
}
public String getContent() {
return content.toString();
}
// 保存状态
public Snapshot save() {
return new Snapshot(content.toString());
}
// 恢复状态
public void restore(Snapshot snapshot) {
this.content = new StringBuilder(snapshot.getContent());
}
}
第2行:定义 TextEditor 类,这是发起人角色
第3行:private StringBuilder content - 使用 StringBuilder 存储文本内容
选择 StringBuilder 而非 String 是因为它支持高效的可变字符串操作
第5-7行:write() 方法 - 向编辑器追加文本
调用 content.append(text) 将新文本添加到末尾
第9-13行:deleteLast() 方法 - 删除末尾的 n 个字符
第10行:检查要删除的字符数是否不超过当前长度(防止越界)
第11行:content.delete(起始位置, 结束位置)
content.length() - n 计算删除的起始位置
content.length() 是删除的结束位置(即末尾)
第15-17行:getContent() 方法 - 返回当前内容的字符串形式
调用 toString() 将 StringBuilder 转换为 String
第20-22行:save() 方法 - 创建快照的关键方法
调用 new Snapshot(content.toString()) 创建当前状态的副本
重要:传递的是字符串副本,不是引用,避免后续修改影响快照
第25-27行:restore() 方法 - 恢复状态的关键方法
从快照中获取保存的内容
用该内容创建新的 StringBuilder 替换当前内容
这样编辑器就回到了快照时的状态
设计要点:
save() 和 restore() 是唯一与 Snapshot 交互的方法
通过字符串副本隔离状态,防止引用共享问题
3. HistoryManager.java - 负责人(Caretaker)
作用:管理快照的历史记录,但不查看或修改快照内容
import java.util.Stack;
// 历史记录管理器(Caretaker)
class HistoryManager {
private final Stack<Snapshot> undoStack = new Stack<>();
public void push(Snapshot snapshot) {
undoStack.push(snapshot);
}
public Snapshot pop() {
if (!undoStack.isEmpty()) {
return undoStack.pop();
}
return null;
}
}
第1行:导入 Stack 类(栈数据结构)
第4行:定义 HistoryManager 类,这是负责人角色
第5行:private final Stack<Snapshot> undoStack - 用栈存储快照
使用栈的原因:撤销操作是**后进先出(LIFO)**的
最近保存的状态最先被撤销
final 确保栈对象本身不被替换
第7-9行:push() 方法 - 将快照压入栈顶
每次编辑后调用此方法保存状态
第11-16行:pop() 方法 - 弹出栈顶的快照
第12行:检查栈是否为空(防御性编程)
第13行:如果非空,弹出并返回栈顶快照
第15行:如果为空,返回 null(表示无法撤销)
设计要点:
HistoryManager 不知道 Snapshot 的内部结构
只负责存储和提供快照,符合"单一职责原则"
4.Client.java - 客户端使用示例
作用:演示如何使用这三个类实现撤销功能
// 客户端使用
public class Client {
public static void main(String[] args) {
TextEditor editor = new TextEditor();
HistoryManager history = new HistoryManager();
editor.write("Hello");
history.push(editor.save());
editor.write(" World");
history.push(editor.save());
editor.write("!");
System.out.println("当前内容: " + editor.getContent()); // Hello World!
// 撤销两次
editor.restore(history.pop());
System.out.println("撤销1: " + editor.getContent()); // Hello World
editor.restore(history.pop());
System.out.println("撤销2: " + editor.getContent()); // Hello
}
}
第4行:创建文本编辑器实例
第5行:创建历史记录管理器实例
第7-8行:第一次编辑和保存
写入 "Hello"
调用 editor.save() 创建快照,并通过 history.push() 保存
第10-11行:第二次编辑和保存
写入 " World"(注意前面的空格)
再次保存快照
第13-14行:第三次编辑但不保存
写入 "!"
打印当前内容:"Hello World!"
第17-18行:第一次撤销
history.pop() 弹出最近的快照(保存的是 "Hello World" 状态)
editor.restore() 恢复到该状态
打印:"Hello World"
第20-21行:第二次撤销
弹出下一个快照(保存的是 "Hello" 状态)
恢复到该状态
打印:"Hello"
操作 | 编辑器内容 | 栈中的快照
------------------------|----------------|------------------
write("Hello") | Hello | [空]
save() & push() | Hello | [Hello]
write(" World") | Hello World | [Hello]
save() & push() | Hello World | [Hello, Hello World]
write("!") | Hello World! | [Hello, Hello World]
pop() & restore() | Hello World | [Hello]
pop() & restore() | Hello | [空]
设计模式的核心思想总结
为什么要这样设计?
封装性保护--TextEditor 的内部实现(StringBuilder)对外部完全隐藏,Snapshot 只暴露必要的 getter,不提供 setter
职责分离--TextEditor:负责业务逻辑(编辑文本),Snapshot:负责状态存储,HistoryManager:负责历史管理
开闭原则--如果要添加"重做"功能,只需扩展 HistoryManager,无需修改现有代码
安全性--快照是不可变的(final),防止意外修改,通过字符串副本隔离状态
更多推荐

所有评论(0)