Go中备忘录模式需手动实现,核心是通过值拷贝或显式深拷贝创建不可变状态快照,用只读接口隔离访问,Originator通过完整替换实现原子恢复,禁用序列化与浅拷贝。

备忘录模式在 Go 中没有内置支持,需手动实现
Go 语言没有类、继承或访问修饰符,无法直接复刻传统面向对象语义下的 Memento 模式(比如私有字段封装 + 窄接口暴露)。所谓“备忘录对象”,本质是**对某时刻状态的不可变快照 + 安全恢复能力**。关键不在名字,而在能否隔离状态、防止外部篡改、避免深拷贝开销失控。
用结构体 + 接口 + 值传递模拟备忘录行为
最常用且符合 Go 风格的做法:定义一个只读接口描述备忘录,用结构体实现它,并让原对象(Originator)负责创建和恢复。所有字段必须导出(否则无法初始化),但通过接口隐藏写操作。
-
Originator的状态字段应为值类型(如int、string、struct{})或深度不可变引用类型(如*sync.Map),避免浅拷贝导致恢复时意外共享 - 备忘录结构体不暴露任何 setter 方法,仅提供
State()或类似只读访问器(如果需要 inspect) - 恢复时,
Originator.Restore(memento Memento)应完全替换内部状态,而非逐字段赋值——防止遗漏或顺序依赖
type Memento interface {
// 空接口即可,或定义只读方法(如 State() map[string]interface{})
}
type originatorState struct {
data string
step int
}
type Originator struct {
state originatorState
}
func (o *Originator) Save() Memento {
// 值拷贝,安全
return &originatorState{data: o.state.data, step: o.state.step}
}
func (o *Originator) Restore(m Memento) {
if m == nil {
return
}
if s, ok := m.(*originatorState); ok {
o.state = *s // 完整替换,非部分更新
}
}
慎用指针或切片字段,否则需显式深拷贝
若 Originator 包含 []byte、map[string]int 或自定义结构体指针,Save() 中直接赋值会共享底层数据。恢复时修改备忘录会影响历史状态,彻底破坏模式语义。
- 对
[]T:用append([]T(nil), src...)或copy(dst, src) - 对
map[K]V:必须新建 map 并遍历复制键值对 - 对嵌套结构体含指针:递归深拷贝,或改用值语义设计(例如用
time.Time替*time.Time)
func (o *Originator) Save() Memento {
// 错误:共享 slice 底层数组
// return &originatorState{data: o.state.data, logs: o.state.logs}
// 正确:深拷贝 logs
logsCopy := make([]string, len(o.state.logs))
copy(logsCopy, o.state.logs)
return &originatorState{
data: o.state.data,
logs: logsCopy,
}
}
不要用 JSON 或 gob 序列化代替内存快照
有人倾向用 json.Marshal 存状态再 json.Unmarshal 恢复,这属于序列化/反序列化,不是备忘录模式。它带来额外 CPU 开销、丢失类型信息(如 time.Time 变成字符串)、无法处理未导出字段或循环引用,且无法保证恢复后对象行为一致(比如方法接收者绑定)。
立即学习“go语言免费学习笔记(深入)”;
- 仅当需要跨进程持久化或调试导出时才考虑序列化
- 内存中状态保存与恢复,永远优先走值拷贝或显式深拷贝
- 若状态极大(GB 级),备忘录模式本身就不适用——该换设计,比如用事件溯源(Event Sourcing)










