Go工厂模式应使用map[string]func() Interface注册制替代if/else,工厂函数须轻量无副作用、返回明确接口、不初始化依赖。

Go 里工厂模式不是“要不要写”,而是“怎么写才不踩坑”——核心就一条:用 map[string]func() Interface 替代 if/else,用注册制替代硬编码,工厂函数必须轻量、无副作用、不初始化依赖。
为什么不能直接写 if/else 工厂函数
常见写法:if typ == "json" { return &JSONProcessor{} } 看似简单,但会快速失控:
- 每新增一种类型,就要改工厂函数,违反开闭原则
- 无法在测试中替换实现(比如 mock 某个 processor)
- 无法支持带参数的构造(如
&FileLogger{filename: "a.log"}),因为 if 分支里写死结构体字面量 - 编译期无法检查所有分支是否覆盖,运行时 panic 风险高
注册表 + 函数值才是 Go 的惯用写法
正确结构是定义一个全局 map[string]func() Processor,配合显式注册(Register)或 init() 注册:
var processors = make(map[string]func() Processor)
func Register(name string, ctor func() Processor) {
processors[name] = ctor
}
func NewProcessor(name string) (Processor, error) {
ctor, ok := processors[name]
if !ok {
return nil, fmt.Errorf("unknown processor: %s", name)
}
return ctor(), nil
}
使用时:
立即学习“go语言免费学习笔记(深入)”;
func init() {
Register("json", func() Processor { return &JSONProcessor{} })
Register("xml", func() Processor { return &XMLProcessor{} })
Register("yaml", func() Processor {
return &YAMLProcessor{Decoder: yaml.NewDecoder(os.Stdin)}
})
}
-
processors存的是函数,不是实例——延迟创建,支持依赖注入和参数化 - 注册可放在
init(),也可在main()开头集中调用,便于控制初始化顺序 - 若需热加载(如插件系统),加
sync.RWMutex包裹读写,但普通服务无需
工厂函数里绝对不能做的事
工厂函数(即注册进 map 的那个 func() Processor)只负责构造对象,其他一律交给具体类型的 NewXXX() 或初始化逻辑:
- ❌ 不要打开文件、连接数据库、读配置——这些该由
NewJSONProcessor(cfg Config)处理,工厂里只调它 - ❌ 不要做校验(如检查端口范围)、不抛错——校验应在具体类型构造时完成,工厂只转发错误
- ❌ 不要返回
interface{}——必须返回明确接口(如Processor),否则失去类型安全 - ❌ 不要在工厂里做单例控制(如
once.Do)——那是单例模式的事,和工厂无关
什么时候该用抽象工厂而不是简单工厂
简单工厂(一个 map + 一个接口)够用 90% 场景;只有当你要一次性创建**一组协同工作的对象**,且它们有明确组合关系时,才需要抽象工厂:
- 例如:不同环境(dev/staging/prod)下,
Logger+Cache+DB必须成套切换 - 关键信号:你发现要写多个平行的
map[string]func() X,且 key 总是一致(比如都用"redis"或"memory"),这时应抽成一个工厂结构体或函数组 - 抽象工厂本身不 new 实例,只返回一组工厂函数:
func() Logger, func() Cache, func() DB
最易被忽略的一点:Go 中抽象工厂往往只是“一组命名一致的工厂函数”,不需要额外接口或结构体包装——过度设计反而增加调用方负担。


















