Go中无原型模式,深拷贝应优先用copier库:支持嵌套、指针、切片、map,跳过未导出字段和不可拷贝类型,错误明确;避免json/gob(性能差、限制多)和手写(易错循环引用)。

Go 里没有原型模式这回事,别被设计模式名词带偏
Go 语言本身不支持类、继承、构造函数重载,也没有 clone() 方法或 Cloneable 接口。所谓“原型模式”在 Go 中只是开发者对“复制一个已有结构体实例并修改部分字段”这一常见操作的模式化描述,不是语言特性,也不该强行套用 UML 类图那一套。
真正要解决的问题只有一个:如何安全、可控、高效地深拷贝一个含指针、切片、map、channel 或嵌套结构体的值?
- 直接赋值(
a := b)只做浅拷贝,共享底层数据 —— 修改a.Fields[0].Name会同时影响b - 用
json.Marshal/Unmarshal最简单但有严重限制:不支持未导出字段、函数、channel、unsafe.Pointer、循环引用,且性能差 - 用
gob同样不支持非导出字段和某些类型,且必须注册类型,不适合通用场景
推荐方案:用 github.com/jinzhu/copier 做零配置深拷贝
它能自动处理嵌套结构体、切片、map、指针解引用,并跳过未导出字段和不可拷贝类型(如 func、chan),错误时明确报错而不是静默失败。
安装:go get github.com/jinzhu/copier
立即学习“go语言免费学习笔记(深入)”;
- 基础用法就是一行:
copier.Copy(&dst, &src),注意传指针(否则结构体内部指针不会被解引用) - 支持忽略字段:
copier.Copy(&dst, &src, copier.Option{Ignore: []string{"CreatedAt", "ID"}}) - 支持自定义字段映射:
copier.CopyWithOption(&dst, &src, copier.Option{Map: map[string]string{"OldName": "NewName"}}) - 如果结构体里有
*time.Time或自定义类型,它默认会调用其Clone()方法(如果存在),否则按值拷贝
自己写 DeepCopy?先确认你真需要,再小心处理循环引用
手写深拷贝函数只在极少数场景合理:比如性能敏感 + 结构体形态固定 + 必须绕过第三方依赖。但绝大多数人低估了循环引用的破坏力 —— 一旦结构体 A 指向 B,B 又指向 A,递归拷贝会栈溢出或无限循环。
- 不要用
reflect.DeepCopy:标准库根本没有这个函数,网上搜到的都是伪代码或旧版实验性 API - 若必须手写,用
map[uintptr]reflect.Value缓存已拷贝对象的地址,每次进入前查缓存,避免重复处理同一内存地址 - 对
interface{}类型要格外小心:需用reflect.TypeOf().Kind()判断底层类型,再分情况处理(比如是struct就递归,是map就新建 + 遍历键值对) - 切片拷贝不能只用
make([]T, len(src))+copy(),因为元素如果是指针或结构体,还得逐个深拷贝
为什么 encoding/gob 和 json 不适合生产环境深拷贝
它们本质是序列化/反序列化,不是拷贝工具。用它们做 DeepCopy 是在用锤子拧螺丝 —— 能转,但代价高、约束多、行为不可控。
-
json.Marshal/Unmarshal会丢掉所有未导出字段(首字母小写),且把time.Time变成字符串再变回,精度可能丢失 -
gob要求所有类型提前注册,且无法跨进程复用编码结果;遇到http.Request这类含io.Reader的结构体会 panic - 两者都比直接内存拷贝慢 10–100 倍,且 GC 压力更大(产生大量临时 []byte)
- 错误信息模糊:
json: cannot unmarshal object into Go value of type int这类报错根本看不出是哪个字段、哪层嵌套出了问题
深拷贝真正的复杂点不在“怎么写”,而在“什么时候不该深拷贝”。比如 HTTP handler 里把请求上下文 ctx 深拷贝?其实应该用 context.WithValue 派生新 ctx。很多你以为需要 DeepCopy 的地方,其实是设计上该用不可变值、值语义或显式所有权转移。










