go中抽象工厂靠函数签名、结构体字段和接口组合自然形成,核心是保证产品族配套使用且工厂只返回接口实例;常见错误是硬套java接口定义,正确做法优先用工厂函数,复杂依赖时才用带配置字段的结构体工厂。

Go 里抽象工厂不是靠“继承接口+实现类”堆出来的,而是靠函数签名、结构体字段和接口组合自然形成的——写错方向,代码会越来越重,而不是更解耦。
抽象工厂在 Go 里到底长什么样
它没有抽象类,也没有 abstract 关键字;它的“抽象”体现在函数返回值的类型约束上,而不是类型层级上。核心就两条:
- 一组产品必须配套使用(比如
Logger+Formatter,或Input+Filter+Output) - 工厂本身不暴露具体类型,只承诺返回满足接口的实例
常见错误是硬套 Java 写法:先定义 type AbstractFactory interface { CreateLogger() Logger; CreateFormatter() Formatter },再搞一堆 WindowsFactory、MacFactory 结构体去实现——这看似“标准”,但很快会发现:CreateLogger() 和 CreateFormatter() 之间毫无约束,调用方仍可能混用 WinLogger 和 JsonFormatter,失去“产品族”意义。
用函数代替接口定义工厂更轻量
多数场景下,直接用工厂函数比定义工厂接口更符合 Go 习惯:
func DevFactory() (Logger, Formatter) {
return &devLogger{}, &textFormatter{}
}
func ProdFactory() (Logger, Formatter) {
return &syslogLogger{}, &jsonFormatter{}
}
这种写法天然保证了配套性:一次调用就拿到一对协同工作的实例。调用方只需依赖函数签名,无需 import 工厂接口或具体类型。
- 测试时可直接替换函数变量:
var NewLoggerAndFormatter = DevFactory,然后在 test 中重置为ProdFactory - 避免结构体嵌套过多字段导致初始化复杂(比如
type LogFactory struct { Env string; Timeout time.Duration }) - 如果真需要状态(如配置来源),用带字段的结构体也行,但方法应返回接口,而非具体类型
结构体工厂适合需要注入依赖的场景
当工厂要读配置、连 DB 或依赖其他服务时,函数就不够用了,这时用结构体封装更清晰:
type LogFactory struct {
cfg Config
}
func (f *LogFactory) NewLogger() Logger {
switch f.cfg.Env {
case "dev":
return &devLogger{level: f.cfg.Level}
case "prod":
return &syslogLogger{addr: f.cfg.SyslogAddr}
}
}
func (f *LogFactory) NewFormatter() Formatter {
switch f.cfg.Env {
case "dev":
return &textFormatter{}
case "prod":
return &jsonFormatter{}
}
}
关键点:
- 结构体字段只存必要依赖,不塞业务逻辑
- 每个
NewXXX()方法内部不做跨产品决策(比如不能在NewLogger()里调f.NewFormatter()) - 避免让结构体实现多个无关接口(如同时实现
LoggerFactory和CacheFactory),那已偏离抽象工厂本意
最容易被忽略的约束:产品间隐式协作
抽象工厂的价值不在“能创建多个对象”,而在于“这些对象能一起工作”。比如 devLogger 可能依赖 textFormatter 的换行格式,syslogLogger 则要求 jsonFormatter 输出严格 JSON。一旦工厂返回的组合破坏这种约定(比如 devLogger + jsonFormatter),运行时可能 silent fail —— 日志内容被截断、字段丢失,却不会 panic。
所以,工厂的契约不是“返回接口”,而是“返回能互相理解的配套实例”。这点没法靠编译器检查,得靠文档、测试和代码审查守住。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











