工厂方法通过统一接口和注册机制解耦对象创建逻辑,新增类型只需实现接口并调用registerparser,避免散落newxxx()和多文件修改。

Go 里怎么用工厂方法解耦对象创建逻辑
工厂方法不是为了炫技,而是当你发现 NewXXX() 散落在十几处、每次加个新类型都要改七八个文件时,它才真正有用。核心是把“谁来造”和“造什么”分开,让新增类型只改一个地方。
典型场景:解析不同格式的配置(JSON、YAML、TOML),每种格式对应一个 ConfigParser 实现,但上层代码不关心具体类型。
- 定义统一接口:
type Parser interface { Parse([]byte) (map[string]interface{}, error) } - 每个实现类型独立包或文件,不互相 import
- 工厂函数返回接口,不暴露具体类型:
func NewParser(format string) (Parser, error) - 避免在工厂里写大段
switch—— 把注册逻辑抽成RegisterParser函数,主程序启动时集中注册
为什么不用 map[string]func() Parser 直接注册
看起来简单,但容易踩两个坑:类型擦除后无法做编译期校验;热加载或插件化时,map 的键名拼错就静默失败。更稳的做法是用函数变量注册,靠 Go 类型系统兜底。
比如:
立即学习“go语言免费学习笔记(深入)”;
var parserCreators = make(map[string]func() Parser)
func RegisterParser(name string, creator func() Parser) {
if _, exists := parserCreators[name]; exists {
panic("duplicate parser name: " + name)
}
parserCreators[name] = creator
}
func NewParser(format string) (Parser, error) {
creator, ok := parserCreators[format]
if !ok {
return nil, fmt.Errorf("unknown parser format: %s", format)
}
return creator(), nil
}
- 注册时传入
func() Parser,编译器会检查返回值是否满足Parser接口 - 调用
creator()是运行时行为,但构造逻辑本身仍受类型约束 - 注意:不要在
init()里注册——测试时难 mock,且跨包初始化顺序不可控
工厂方法和依赖注入容器冲突吗
不冲突,但职责要分清。工厂方法负责「同类对象的多态创建」,比如一堆 Parser;DI 容器负责「跨层级依赖组装」,比如把 Parser 塞进 Service。混用时最容易出问题的是生命周期管理。
- 如果 DI 容器管理的是单例
Parser,那工厂方法就退化为配置路由,别再让它 new 实例 - 如果每次都需要新实例(如带不同选项的
HTTPClient),工厂方法仍是首选,DI 容器应把工厂本身注入进去,而不是注入一堆具体类型 - 警惕:用
dig或wire自动生成工厂代码时,生成的函数签名可能漏掉错误返回,导致 panic 而非可控错误
扩展新类型时最常漏掉的三件事
加完 struct 和 Parse 方法,跑测试通过就提交?大概率线上出问题。
- 忘记调用
RegisterParser("new_format", func() Parser { return &NewFormatParser{} }) - 新类型实现了接口,但没导出字段或方法——比如
type NewFormatParser struct{ data map[string]interface{} },data小写,反序列化失败却无提示 - 没加单元测试覆盖工厂路由逻辑,只测了具体实现。结果
NewParser("new_format")返回nil,上游 panic
工厂方法真正的复杂点不在写法,而在注册时机、错误传播路径、以及团队对“谁负责注册”的约定是否清晰。没人管注册,再多的模式也只是空架子。










