备忘录模式是为有状态对象设计的快照机制,核心是发起人自主保存/恢复整体状态,备忘录仅作不可变数据载体,管理者只负责栈操作而不触碰状态内容。

备忘录模式不是给“变量”单独用的,而是为有状态的对象设计的。所谓“关键变量的撤销点”,本质是对象整体状态在某个时间点的快照——比如编辑器的 content、font、fontSize,或表单的 name、email、isChecked 等字段组合。直接对单个变量套用备忘录,会破坏封装、增加耦合,也失去模式本意。
明确三个角色各自管什么
这是落地前提,不能跳过:
-
发起人(Originator):你要保护的那个对象,比如
FormModel或ConfigEditor。它必须自己定义“哪些字段算关键状态”,并提供saveState()和restoreFrom(memento)方法。 -
备忘录(Memento):一个轻量、只读、不可变的数据载体。它不暴露 setter,也不参与业务逻辑,只存发起人交付的值(如
Map<String, Object>或结构化 DTO)。Java 中常用私有内部类实现访问控制;Python 可用namedtuple或@dataclass(frozen=True)。 -
管理者(Caretaker):负责堆栈管理(undo/redo),但不碰状态内容。它只调用发起人的
saveState()获取备忘录,再交给发起人去restoreFrom()。它不知道字段名、类型或含义。
关键变量怎么选才真正“关键”
不是所有字段都要记。重点抓两类:
-
用户显式操作影响的字段:比如点击“加粗”按钮改了
isBold,输入框修改了title。这些变更应触发一次saveState()。 -
可能被批量覆盖的字段组:比如导入配置时一次性重置
timeout、retries、host三个字段。这种场景下,一次保存比逐个记录更安全、更可回溯。 - 避开:
临时计算字段(如isValid)、UI 辅助状态(如isEditing)、缓存字段(如cachedResult)。它们不该进备忘录。
一次典型操作的完整链路
以修改表单项为例,代码逻辑流如下:
- 用户在输入框输入 “Alice”,触发 onChange → 调用
formModel.setTitle("Alice") - 紧接着调用
history.push(formModel.saveState())→ 发起人打包当前title、email、agreeToTos成备忘录,管理者压入 undo 栈,清空 redo 栈 - 用户点撤销 → 管理者弹出栈顶备忘录,传给
formModel.restoreFrom(memento)→ 发起人用自己的私有逻辑把三个字段原样写回 - 整个过程,管理者从不读取
memento.title,发起人也不依赖管理者做判断
避免踩内存和一致性坑
实战中最容易翻车的两个点:
-
深拷贝没做干净:如果状态里含 List 或嵌套对象,
saveState()必须递归克隆,否则 restore 时改的是同一份引用,撤销就失效。Java 用new ArrayList(original),Python 用copy.deepcopy()(谨慎评估性能)。 - 状态膨胀失控:连续打字每键都 save,1 秒 20 次,内存爆炸。解决办法是加节流:只在 onBlur、onSubmit 或间隔 500ms 无操作后才存;或合并相邻小变更(如连续输入“abc”记为一次,而非 a→ab→abc 三次)。

















