备忘录模式通过EditorMemento(不可变快照)、TextEditor(Originator,负责保存/恢复状态)和HistoryManager(Caretaker,双栈管理撤销重做)实现文字编辑器的撤销/恢复功能,核心是状态快照而非操作日志。

用备忘录模式(Memento Pattern)实现文字编辑器的撤销/恢复功能,核心是把编辑器的状态(如当前文本、光标位置)快照化并独立保存,不破坏编辑器原有封装。下面直接说怎么落地。
1. 定义备忘录类(Memento),只存状态,不暴露内部
备忘录应是不可变的纯数据载体,由编辑器自己创建和读取,外部(比如撤销管理器)只能持有它,不能修改或访问细节。
- 用私有构造函数 + 公共 getter(仅读取)保证封装性
- 通常只存关键状态:文本内容、光标索引、选区范围(按需)
- 避免存大对象(如图片缓存)、线程相关字段或可变集合
public class EditorMemento {
private final String content;
private final int cursorPosition;
<pre class="brush:php;toolbar:false;">private EditorMemento(String content, int cursorPosition) {
this.content = content;
this.cursorPosition = cursorPosition;
}
public static EditorMemento saveState(String content, int cursorPosition) {
return new EditorMemento(content, cursorPosition);
}
public String getContent() { return content; }
public int getCursorPosition() { return cursorPosition; }}
2. 编辑器类(Originator)负责生成和恢复状态
编辑器自己维护当前状态,并提供两个关键方法:saveToMemento() 创建快照,restoreFromMemento() 回滚到指定快照。
- saveToMemento() 应在每次用户执行“有意义的操作”后调用(如输入字符、删除、粘贴),不是每敲一个键都存(可做简单合并优化)
- restoreFromMemento() 直接覆盖当前状态,不触发额外逻辑(如事件通知)——那是上层控制器的事
- 状态字段建议用 final 或不可变类型(如 String),避免被意外修改
public class TextEditor {
private String content = "";
private int cursorPosition = 0;
<pre class="brush:php;toolbar:false;">public void insert(char c) {
content = content.substring(0, cursorPosition) + c + content.substring(cursorPosition);
cursorPosition++;
}
public void delete() {
if (cursorPosition > 0) {
content = content.substring(0, cursorPosition - 1) + content.substring(cursorPosition);
cursorPosition--;
}
}
public EditorMemento saveToMemento() {
return EditorMemento.saveState(content, cursorPosition);
}
public void restoreFromMemento(EditorMemento memento) {
this.content = memento.getContent();
this.cursorPosition = memento.getCursorPosition();
}
// getter for UI display
public String getContent() { return content; }
public int getCursorPosition() { return cursorPosition; }}
3. 撤销管理器(Caretaker)负责存取快照栈
它不碰编辑器内部,只管 push(保存)、pop(撤销)、peek(重做)备忘录对象。典型用两个栈:undoStack 存已执行操作的历史,redoStack 存刚撤销过的操作。
- 每次执行新操作前,先调 editor.saveToMemento(),再 push 到 undoStack
- 撤销时:pop undoStack → restore 到 editor → push 该 memento 到 redoStack
- 重做时:pop redoStack → restore 到 editor → push 回 undoStack(可选,方便连续重做)
- 注意边界检查:栈空时不执行 pop,避免异常
public class HistoryManager {
private final Stack<editormemento> undoStack = new Stack();
private final Stack<editormemento> redoStack = new Stack();
<pre class="brush:php;toolbar:false;">public void addMemento(EditorMemento memento) {
undoStack.push(memento);
redoStack.clear(); // 新操作使重做失效
}
public EditorMemento undo(TextEditor editor) {
if (undoStack.isEmpty()) return null;
EditorMemento memento = undoStack.pop();
editor.restoreFromMemento(memento);
redoStack.push(memento);
return memento;
}
public EditorMemento redo(TextEditor editor) {
if (redoStack.isEmpty()) return null;
EditorMemento memento = redoStack.pop();
editor.restoreFromMemento(memento);
undoStack.push(memento);
return memento;
}
}
4. 在实际交互中触发状态管理
不要让编辑器自己决定什么时候存状态。控制权交给 UI 层或命令层:
- 绑定 Ctrl+Z / Ctrl+Y 到 HistoryManager.undo()/redo()
- 每个用户动作封装为 Command(命令模式),execute() 前先保存快照,undo() 时直接恢复
- 避免自动定时保存;撤销粒度由操作语义决定(例如一次“粘贴”算一步,不管里面多少字符)
- 可加限制:undoStack 最多存 50 个快照,防止内存溢出
不复杂但容易忽略:备忘录本质是“状态快照”,不是“操作日志”。它不记录“做了什么”,只记录“做完后长啥样”。适合状态变化频繁但历史较短的场景,比如富文本编辑器的基础撤销。如果需要支持协同编辑或持久化历史,就得换更重的方案(如 OT 或 CRDT)。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











