Go中备忘录模式的核心难点是值语义与指针语义混淆导致浅拷贝引发状态共享;推荐用json.Marshal/Unmarshal实现安全快照,或手动深拷贝关键字段,禁用unsafe.Pointer回滚。
Go 没有内置的深拷贝机制,直接用结构体赋值(backup := current)只做浅拷贝。如果结构体含 []byte、map、slice 或指向堆内存的指针,回滚时会意外共享状态,导致“看似回滚了,实际改了备份”。必须显式隔离可变数据。
json.Marshal/Unmarshal 实现安全快照(适合中小对象)这是最稳妥的默认方案:序列化天然切断引用关系,且对嵌套结构、切片、map 透明支持。缺点是性能开销和不支持未导出字段或函数类型。
sync.Mutex 等不可序列化字段Timestamp time.Time 字段type Editor struct {
Content string
Cursor int
}
type Memento struct {
Content string
Cursor int
Timestamp time.Time
}
func (e *Editor) Save() *Memento {
return &Memento{
Content: e.Content,
Cursor: e.Cursor,
Timestamp: time.Now(),
}
}
func (e *Editor) Restore(m *Memento) {
e.Content = m.Content
e.Cursor = m.Cursor
}
copy() 处理切片(高频修改场景)当结构体含大容量 []byte 或频繁回滚时,json 序列化太重。此时应为关键字段提供显式复制逻辑:
[]byte:用 copy(dst, src) 或 append([]byte(nil), src...)
map[string]int:新建 map 并遍历赋值Clone() 方法(需自行实现)type Document struct {
Lines []string
Meta map[string]string
}
func (d *Document) Save() *Document {
linesCopy :=
make([]string, len(d.Lines))
copy(linesCopy, d.Lines)
metaCopy := make(map[string]string)
for k, v := range d.Meta {
metaCopy[k] = v
}
return &Document{
Lines: linesCopy,
Meta: metaCopy,
}
}
unsafe.Pointer 回滚原始内存?别这么做有人试图用 unsafe 记录结构体地址+大小再 memcpy,这在 Go 中极其危险:GC 可能移动对象,且 unsafe 绕过编译器检查,一旦结构体字段增减或对齐变化,回滚就会静默破坏内存。Go 的内存模型不保证结构体布局跨版本稳定,生产环境必须杜绝此类操作。
真正需要极致性能的场景(如游戏帧状态),应改用预分配缓冲池 + 显式状态转移函数,而不是依赖底层内存快照。