Go中工厂模式应返回接口而非指针,通过指针接收者实现接口以支持方法调用和避免大结构体拷贝,关键在于接口+指针的松耦合设计,而非单纯使用指针。

为什么不能直接用指针实现“工厂模式”
Go 语言没有类、继承和构造函数,所谓“工厂模式”在 Go 里本质是函数返回结构体实例(或其指针),而非模拟面向对象的抽象工厂。用 *T 本身不构成模式,关键在于如何封装创建逻辑、解耦使用者与具体类型。强行用指针做“多态基类”反而容易写出难以维护的代码——比如定义一个空接口指针作为“产品基类”,再靠类型断言分发,这既丧失编译期检查,又违背 Go 的惯用法。
用指针配合接口才是 Go 工厂的正确姿势
Go 中典型的工厂函数返回的是接口类型,内部 new 出具体结构体并返回其指针(因为多数结构体需要方法集,而值接收者无法修改原值,指针接收者更通用)。重点不在指针本身,而在指针 + 接口组合带来的松耦合。
- 工厂函数签名应为
func() ProductInterface,而非func() *Product - 具体产品类型需实现接口,且方法接收者通常用指针(
func (p *Concrete) Do()),否则无法满足接口要求 - 若产品结构体较大,返回
*T避免拷贝;若很小(如type ID int),返回值也无妨
type Service interface {
Start()
}
type HTTPService struct {
port string
}
func (HTTPService) Start() { / ... */ }
func NewService(kind string) Service {
switch kind {
case "http":
return &HTTPService{port: ":8080"} // 返回指针以满足接口
default:
return nil
}
}
避免常见陷阱:nil 指针调用与接口零值
工厂返回接口时,如果内部 new 失败却返回了 nil,调用方直接调用方法会 panic:panic: runtime error: invalid memory address or nil pointer dereference。这不是指针用错了,而是没处理创建失败的契约。
- 不要依赖接口变量是否为 nil 来判断有效性——接口变量本身非 nil,但底层 concrete 值可能是 nil
- 工厂应明确失败路径:返回
(Service, error),或用哨兵值(如var ErrInvalidKind = errors.New("invalid kind")) - 若必须返回可空接口,调用方应先做类型断言并判空:
if s, ok := factory() .(*HTTPService); ok && s != nil { ... }
什么时候该传入指针参数而不是返回指针
工厂模式关注“创建”,但实际开发中常遇到初始化依赖外部状态的场景。此时不应让工厂函数承担所有依赖组装,而应接受配置指针或选项函数——这是更灵活、更测试友好的做法。
立即学习“go语言免费学习笔记(深入)”;
- 避免工厂函数内部读取全局配置(如
os.Getenv),改为接收*Config或Option函数 - 例如:
func NewService(cfg *Config) Service比func NewService() Service更可控 - 若配置字段多,用选项模式(Functional Options)更清晰:
NewService(WithPort(":3000"), WithTimeout(5*time.Second))
指针在这里只是传递配置的载体,真正决定工厂行为的是设计契约,不是内存地址。










