工厂函数必须返回接口类型而非具体结构体指针,如func(string) Shape而非func(string) *Circle,以隐藏实现细节、支持扩展而不改调用方代码,并通过接口契约(如仅含Area方法)确保行为一致性与编译期类型安全。

工厂函数必须返回接口类型,而不是具体结构体指针
返回 *Circle 或 Rectangle 这类具体类型,就等于把实现细节暴露给调用方,后续加个 Square 就得改所有调用点。真正起作用的是接口契约——比如 Shape 接口只声明 Area() 方法,调用方只依赖这个行为,不关心背后是圆是方。
- 接口定义要克制:只放业务真正需要的方法,别为了“看起来像 Java”硬塞
Clone()Serialize() - 工厂函数签名必须是
func(string) Shape,不是func(string) *Circle - 如果返回
interface{},等于放弃编译检查,IDE 补全失效,错误拖到运行时才爆
注册式工厂要用 map[string]func() Interface,别存实例
常见错误是写成 map[string]Shape,在 Register 时就执行构造函数,导致对象提前创建、资源提前占用、无法按需传参。正确做法是只存函数值,让 NewShape("circle") 在真正需要时才调用 func() Shape。
- 注册必须在
init()或main()开头完成,否则NewShape可能查不到 key - 全局 map 默认非并发安全;若仅启动期注册,用
sync.Once初始化即可;若需热加载,得套sync.RWMutex - 别在工厂函数里做重操作(如连 DB、读大文件),那是具体类型自己
NewXXX()该干的事
带配置的工厂得用选项模式,别堆参数
当初始化需要超时、重试、日志级别等可选配置时,硬塞 5 个参数进工厂函数会迅速失控。Go 社区通用解法是选项模式:定义一个私有函数类型 type option func(*Client),每个配置项封装成独立函数,如 WithTimeout(30 * time.Second)。
- 工厂函数接收变长
option参数,内部按顺序应用,后传入的覆盖先传入的 - option 函数只负责赋值字段或调 setter,禁止副作用(比如启动 goroutine、打开文件)
- 零值默认配置应在工厂内部设好,避免调用方漏传关键项导致 panic
工厂函数命名必须用 NewXXX,且返回指针
NewClient 是 Go 的事实标准命名,IDE、go doc、linter 都认这个约定。返回指针不只是为了性能(避免大结构体拷贝),更是语义表达:这个对象预期会被修改,且生命周期由调用方管理。
立即学习“go语言免费学习笔记(深入)”;
- 如果初始化可能失败(比如校验配置失败、连接 refused),必须返回
(*Client, error),别用panic - 不要写
new(Client)—— 它只分配内存,不做任何初始化,字段全是零值,极易引发后续 panic - 工厂函数里不做依赖注入的“自动发现”,所有外部依赖(如
*http.Client、Logger)必须显式传入,否则测试时无法替换 mock
工厂最易被忽略的点,是把“创建逻辑收口”误当成“把 if/else 包一层”。真正的价值在于:让新增类型不改旧代码、让测试可插拔、让配置可运行时切换。一旦发现工厂函数里开始拼接字符串路径、解析 YAML、调远程配置中心,说明它已经越界了——那些该交给上层初始化流程或 DI 容器处理。


















