如何在Golang中实现工厂模式_Golang工厂模式对象创建示例

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

什么是Go里的“工厂函数”而非“工厂类”

Go没有类和继承,所以传统OOP中的抽象工厂、工厂方法模式无法直接套用。Go的工厂模式本质是一组返回接口类型值的函数,靠组合和接口隐式满足契约,不是靠类型系统强制实现。别试图写 Factory 结构体去“new”其他结构体——这反而是冗余设计。

用 interface + 工厂函数解耦对象创建

典型场景:需要根据配置或参数创建不同行为的 Logger(比如 FileLoggerConsoleLogger),但调用方只依赖 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 一调用就打开文件句柄或连接数据库,会导致:

  • 单元测试难 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{} + 具体构造函数的组合。

泛型工厂只适合“类型参数化但行为一致”的场景,比如容器、工具函数,而不是业务对象的策略替换。