工厂函数应返回接口而非具体结构体,参数用配置结构体,避免硬编码默认值和init注册,不承担业务校验与副作用。

Go 里没有构造函数,但工厂函数是统一创建对象最自然、最可控的方式——关键不是“能不能写”,而是“返回什么类型”和“怎么传参”。
工厂函数必须返回接口,而不是具体结构体
如果工厂返回 *MyService,调用方就和这个结构体强绑定;一旦要换实现(比如从内存缓存换成 Redis 缓存),所有调用点都得改。而返回一个 Service 接口,只要新类型实现了相同方法,替换就只是改一行工厂调用。
- 接口定义要聚焦行为,比如
type Service interface{ Do() error } - 工厂签名应为
func NewService(cfg ServiceConfig) Service,不是func NewService() *realService - 嵌入结构体(如
*dbRepo)不改变类型关系:哪怕typeARepo嵌入了dbRepo,*typeARepo也不是*dbRepo,不能互相赋值
配置参数优先用结构体,别用零散字符串或 map
用 string 或 map[string]interface{} 传参看着灵活,实际是埋雷:字段名拼错、类型不一致、IDE 无法提示、测试难 mock。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 定义导出的配置结构体,字段名清晰可读:
type DBConfig struct{ Addr string; Timeout time.Duration } - 工厂内部只取需要的字段,多传不报错,少传编译即拦住
- 避免在工厂里硬编码默认值(如
timeout: 5 * time.Second),应在调用方显式设置或用 Option 函数封装
不要在 init 里注册工厂,除非构造逻辑完全无依赖
像 processors["json"] = func() Processor { return &JSONProcessor{} } 这种注册方式,看似简洁,但一旦 JSONProcessor 初始化需要 DB 连接或配置加载,init 阶段就会 panic 或死锁。
- 推荐显式注册:提供
RegisterProcessor(name string, ctor func() Processor)函数 - 让主程序在配置加载完成、依赖就绪后再调用注册,控制初始化时序
- 注册表本身用
map[string]func() Processor,不是map[string]Processor——后者会立即实例化,失去按需创建的意义
最容易被忽略的一点:工厂函数本身不该做业务校验或副作用操作。它只负责组装对象;校验应放在结构体方法里(如 Validate() error),副作用(如连接池初始化)应延迟到第一次使用时(或由调用方明确触发)。否则工厂就成了黑盒,难以测试,也违背单一职责。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










