Go中“工厂函数”是返回接口值的轻量函数,通过接口隐式满足契约,避免冗余Factory结构体;典型如NewLogger根据参数返回不同Logger实现,且工厂函数不应含heavy初始化。

什么是Go里的“工厂函数”而非“工厂类”
Go没有类和继承,所以传统OOP中的抽象工厂、工厂方法模式无法直接套用。Go的工厂模式本质是一组返回接口类型值的函数,靠组合和接口隐式满足契约,不是靠类型系统强制实现。别试图写 Factory 结构体去“new”其他结构体——这反而是冗余设计。
用 interface + 工厂函数解耦对象创建
典型场景:需要根据配置或参数创建不同行为的 Logger(比如 FileLogger、ConsoleLogger),但调用方只依赖 Logger 接口。
- 先定义统一接口:
type Logger interface { Log(string) } - 每个具体实现不暴露字段,只导出构造函数:
NewConsoleLogger()、NewFileLogger(filename string) - 工厂函数集中封装判断逻辑,例如:
NewLogger(kind string, cfg map[string]string) Logger
这样调用方完全不知道背后是哪个结构体,也不会 import 具体实现包(除非必须传参)。
type Logger interface {
Log(msg string)
}
type consoleLogger struct{}
func (c *consoleLogger) Log(msg string) {
fmt.Println("[CONSOLE]", msg)
}
func NewConsoleLogger() Logger {
return &consoleLogger{}
}
type fileLogger struct {
filename string
}
func (f *fileLogger) Log(msg string) {
os.WriteFile(f.filename, []byte(msg), 0644)
}
func NewFileLogger(filename string) Logger {
return &fileLogger{filename: filename}
}
func NewLogger(kind string, cfg map[string]string) Logger {
switch kind {
case "console":
return NewConsoleLogger()
case "file":
return NewFileLogger(cfg["filename"])
default:
return NewConsoleLogger()
}
}
为什么不要在工厂里做 heavy initialization
工厂函数应轻量、无副作用、可测试。如果 NewFileLogger 一调用就打开文件句柄或连接数据库,会导致:
立即学习“go语言免费学习笔记(深入)”;
- 单元测试难 mock(你得提前建好真实文件或DB)
- 对象创建失败时错误难以归因(是参数错?磁盘满?权限不足?)
- 违反“创建即可用”的直觉——用户拿到
Logger却不能立刻Log
正确做法:工厂只组装结构体,把初始化延迟到首次使用(如 Log 内部 lazy open),或拆出显式的 Init() 方法由调用方控制时机。
泛型工厂函数在 Go 1.18+ 的适用边界
泛型能简化“同构类型”的工厂,比如统一创建带缓冲的 channel:func NewChan[T any](size int) chan T。但它不适合替代接口工厂。
常见误用:写 func NewService[T Service]() T —— 这要求 T 必须有零值且可直接返回,丧失了多态能力;也无法隐藏具体实现细节。真正需要多态时,还是得回到 interface{} + 具体构造函数的组合。
泛型工厂只适合“类型参数化但行为一致”的场景,比如容器、工具函数,而不是业务对象的策略替换。










