备忘录模式(Memento Pattern)
1. 知识点总结
备忘录模式是一种行为型设计模式,核心思想是:
在不破坏对象封装性的前提下,捕获对象的内部状态并保存在外部,以便后续能够将对象恢复到之前的状态。
它最常见的应用就是撤销(Undo)/重做(Redo)功能,比如文本编辑器、游戏存档、数据库事务回滚等。
模式中有三个核心角色:
| 角色 | 职责 |
|---|---|
| Originator(发起人) | 拥有需要保存的状态,提供创建备忘录和恢复状态的方法 |
| Memento(备忘录) | 封装 Originator 的内部状态,对外隐藏细节 |
| Caretaker(管理者) | 负责保存和恢复备忘录,不直接操作备忘录内容 |
适用场景
- 需要实现撤销/重做功能;
- 需要保存对象的历史快照,支持回滚;
- 事务执行失败时需要还原到执行前的状态。
优缺点
| 优点 | 缺点 |
|---|---|
| 不破坏封装性,外部无法随意修改备忘录状态 | 频繁保存大对象会消耗较多内存 |
| 状态保存逻辑与业务逻辑分离 | 管理者需要维护备忘录集合,可能引入额外复杂度 |
| 容易实现多步撤销/重做 | 需要谨慎处理深拷贝与浅拷贝问题 |
2. 示例代码
下面用一个简单的文本编辑器来演示:每次写入后保存快照,误操作后可以通过历史栈撤销。
package mainimport "fmt"// Editor 是发起人(Originator),拥有需要被保存的内部状态。
type Editor struct {content string
}func (e *Editor) Write(text string) {e.content += text
}func (e *Editor) Content() string {return e.content
}// Save 创建一份备忘录,封装当前内容。
func (e *Editor) Save() Memento {return Memento{snapshot: e.content}
}// Restore 根据备忘录恢复状态。
func (e *Editor) Restore(m Memento) {e.content = m.snapshot
}// Memento 是备忘录,只暴露给 Caretaker 和 Originator 使用。
// 这里通过未导出字段限制外部直接修改快照内容。
type Memento struct {snapshot string
}// History 是管理者(Caretaker),负责维护备忘录的历史栈。
type History struct {stack []Memento
}func (h *History) Save(m Memento) {h.stack = append(h.stack, m)
}func (h *History) Undo() (Memento, bool) {if len(h.stack) == 0 {return Memento{}, false}last := h.stack[len(h.stack)-1]h.stack = h.stack[:len(h.stack)-1]return last, true
}func main() {editor := &Editor{}history := &History{}editor.Write("Hello ")history.Save(editor.Save())fmt.Printf("写入后: %q\n", editor.Content())editor.Write("World!")history.Save(editor.Save())fmt.Printf("再次写入后: %q\n", editor.Content())editor.Write(" 这是多余内容")fmt.Printf("误操作后: %q\n", editor.Content())if m, ok := history.Undo(); ok {editor.Restore(m)fmt.Printf("撤销一次后: %q\n", editor.Content())}if m, ok := history.Undo(); ok {editor.Restore(m)fmt.Printf("再撤销一次后: %q\n", editor.Content())}
}
3. 运行结果
写入后: "Hello "
再次写入后: "Hello World!"
误操作后: "Hello World! 这是多余内容"
撤销一次后: "Hello World!"
再撤销一次后: "Hello "
4. 关键点复盘
- 封装性是第一原则:备忘录的
snapshot字段没有导出,外部(包括 Caretaker)不能直接修改快照内容,只能通过 Originator 的Save/Restore来操作。 - Caretaker 只负责保管:
History不知道备忘录里具体存了什么,也不应该去解析它,这样 Originator 的内部结构变化不会影响历史管理逻辑。 - 栈是实现撤销的常用结构:每次保存时入栈,撤销时出栈并恢复;如果要支持重做,可以再加一个 redo 栈。
- 注意深拷贝:如果 Originator 的状态包含引用类型(切片、Map、指针),保存备忘录时需要进行深拷贝,否则快照会被后续修改污染。
- 与命令模式的区别:备忘录模式通过保存状态快照来撤销;命令模式通常通过记录逆操作来撤销。状态简单时用备忘录更直接,操作复杂时命令模式更灵活。