Go工厂模式用接口+函数返回具体实例解耦创建与使用,应返回明确接口而非interface{},通过注册表替代if/else,支持选项函数或配置结构体传参,注册需线程安全,工厂应轻量无副作用。

Go 语言没有类和继承,所谓“工厂模式”不是靠抽象基类+子类实现的,而是用接口 + 函数返回具体结构体实例的方式达成——核心是解耦创建逻辑与使用逻辑。
为什么 Go 的工厂函数通常返回 interface{}
工厂函数返回 interface{} 是常见误区。正确做法是返回一个明确的接口类型(比如 Shape),让调用方只依赖行为契约,不感知具体结构体。返回 interface{} 会丢失类型信息,后续必须强制类型断言,反而增加耦合和 panic 风险。
实际做法:
- 先定义接口(如
type Shape interface { Area() float64 }) - 工厂函数签名应为
func NewShape(kind string) Shape,而非func NewShape(kind string) interface{} - 每个具体类型(
Circle、Rectangle)实现该接口
如何避免工厂函数里堆砌 if/else 判断
当产品种类增多,if kind == "circle" { return &Circle{} } 这类硬编码分支会难以维护。更可持续的做法是用注册表 + 映射:
示例关键结构:
var creators = make(map[string]func() Shape)
func Register(name string, creator func() Shape) {
creators[name] = creator
}
func NewShape(kind string) Shape {
if c, ok := creators[kind]; ok {
return c()
}
panic("unknown shape: " + kind)
}
使用时提前注册:
func init() {
Register("circle", func() Shape { return &Circle{} })
Register("rect", func() Shape { return &Rectangle{} })
}
工厂是否需要接收配置参数?怎么传才安全
如果具体类型初始化需要参数(比如 Circle{Radius: 5.0}),工厂函数不应直接接收原始字段(如 func NewCircle(radius float64) Shape),否则每新增字段都要改签名。推荐两种方式:
- 用选项函数(functional options):定义
type Option func(*Circle),工厂接收变长Option参数,内部按需应用 - 用配置结构体:定义
type CircleConfig struct { Radius float64; Unit string },工厂接收CircleConfig,保持函数签名稳定
避免把 map[string]interface{} 当万能参数传入工厂——它绕过了编译检查,运行时易出错且无法被 IDE 提示。
并发环境下工厂函数要注意什么
注册表(如上文的 creators map)若在运行时动态注册,多个 goroutine 同时调用 Register 会引发 panic。必须加锁或限定仅在 init() 中注册。
工厂函数本身通常是无状态的,但若内部缓存了资源(如复用连接池、预分配对象池),就要注意:
- 缓存读写需同步(
sync.RWMutex或sync.Map) - 避免在工厂里做耗时操作(如磁盘 I/O、网络请求),否则会拖慢所有调用方
多数情况下,工厂就该是个轻量、纯内存、无副作用的构造函数——复杂初始化逻辑应该下沉到具体类型的 NewXXX() 方法里,而不是塞进工厂分支中。










