Skip to content

工厂模式

工厂模式是创建型模式中最常见的一族,包含三种形态:简单工厂、工厂方法、抽象工厂。在 Java 里,这三者的区分很明确,往往需要定义多个抽象类和接口。在 Go 里,由于「NewXxx 函数 + 接口」已经是默认写法,工厂模式常常以更轻量的形式出现。本章我们逐一讲解三种形态,并介绍 Go 1.18+ 泛型如何让工厂更类型安全。

一、简单工厂模式

1. 意图

定义一个工厂函数,根据入参返回不同的具体类型。客户端不需要知道具体类型的构造细节,只需要告诉工厂「我要哪种」。

2. Go 实现:NewXxx + switch

Go 的简单工厂就是一个带 switchNewXxx 函数:

go
package main

import "fmt"

// Payment 是抽象产品(接口)
type Payment interface {
	Pay(amount float64) error
}

// 具体产品
type Alipay struct{}

func (Alipay) Pay(amount float64) error {
	fmt.Printf("Alipay 支付 %.2f\n", amount)
	return nil
}

type WechatPay struct{}

func (WechatPay) Pay(amount float64) error {
	fmt.Printf("微信支付 %.2f\n", amount)
	return nil
}

type BankCardPay struct{}

func (BankCardPay) Pay(amount float64) error {
	fmt.Printf("银行卡支付 %.2f\n", amount)
	return nil
}

// NewPayment 是简单工厂
func NewPayment(method string) (Payment, error) {
	switch method {
	case "alipay":
		return Alipay{}, nil
	case "wechat":
		return WechatPay{}, nil
	case "bank":
		return BankCardPay{}, nil
	default:
		return nil, fmt.Errorf("不支持的支付方式: %s", method)
	}
}

func main() {
	for _, m := range []string{"alipay", "wechat", "bank", "bitcoin"} {
		p, err := NewPayment(m)
		if err != nil {
			fmt.Println("错误:", err)
			continue
		}
		p.Pay(99.9)
	}
}

简单工厂的优缺点:

  • 优点:客户端只跟 NewPayment 打交道,新增支付方式只改工厂,客户端代码不动。
  • 缺点:每加一种产品都要改 switch,违反开闭原则。产品种类多时工厂会变得臃肿。

3. 用 map 优化简单工厂

当产品种类多且创建逻辑简单时,可以用 map 替代 switch,让代码更易扩展:

go
package main

import "fmt"

type Payment interface {
	Pay(amount float64) error
}

type Alipay struct{}

func (Alipay) Pay(amount float64) error {
	fmt.Printf("Alipay 支付 %.2f\n", amount)
	return nil
}

type WechatPay struct{}

func (WechatPay) Pay(amount float64) error {
	fmt.Printf("微信支付 %.2f\n", amount)
	return nil
}

// 注册表:把字符串映射到工厂函数
var paymentRegistry = map[string]func() Payment{
	"alipay": func() Payment { return Alipay{} },
	"wechat": func() Payment { return WechatPay{} },
}

func NewPayment(method string) (Payment, error) {
	factory, ok := paymentRegistry[method]
	if !ok {
		return nil, fmt.Errorf("不支持: %s", method)
	}
	return factory(), nil
}

// RegisterPayment 允许其他包注册新的支付方式(开闭原则)
func RegisterPayment(method string, factory func() Payment) {
	paymentRegistry[method] = factory
}

func main() {
	// 运行时注册新支付方式
	RegisterPayment("bank", func() Payment {
		return struct{ Payment }{} // 简化示例,实际应实现 Pay
	})

	p, err := NewPayment("alipay")
	if err != nil {
		fmt.Println(err)
		return
	}
	p.Pay(50)
}

这种「注册表模式」在 Go 标准库里非常常见(database/sqlimage 包都是),它让简单工厂具备了「开闭原则」特性:新增产品不需要改工厂代码,只需要 Register

二、工厂方法模式

1. 意图

定义一个创建对象的接口,由子类决定实例化哪个类型。工厂方法把「使用哪种产品」的决策延迟到子类(在 Go 里是实现工厂接口的具体类型)。

简单工厂 vs 工厂方法的核心区别:

  • 简单工厂:一个工厂函数 + switch,所有产品在一个地方创建。
  • 工厂方法:每个产品有自己的工厂,工厂实现同一个工厂接口。

2. Go 实现:接口 + 工厂接口

go
package main

import "fmt"

// === 产品接口 ===
type Notification interface {
	Send(to, msg string) error
}

// === 具体产品 ===
type EmailNotification struct{}

func (EmailNotification) Send(to, msg string) error {
	fmt.Printf("发邮件给 %s: %s\n", to, msg)
	return nil
}

type SMSNotification struct{}

func (SMSNotification) Send(to, msg string) error {
	fmt.Printf("发短信给 %s: %s\n", to, msg)
	return nil
}

// === 工厂接口 ===
type NotificationFactory interface {
	Create() Notification
}

// 具体工厂:每个产品配一个工厂
type EmailFactory struct{}

func (EmailFactory) Create() Notification { return EmailNotification{} }

type SMSFactory struct{}

func (SMSFactory) Create() Notification { return SMSNotification{} }

// 业务代码依赖工厂接口,不依赖具体工厂
func notifyUser(f NotificationFactory, to, msg string) {
	n := f.Create()
	n.Send(to, msg)
}

func main() {
	notifyUser(EmailFactory{}, "alice@example.com", "你好")
	notifyUser(SMSFactory{}, "13800000000", "验证码 1234")
}

3. 与 Java 抽象工厂的区别

Java 的工厂方法通常长这样:一个抽象类 NotificationFactory,里面有一个抽象方法 create(),每个具体工厂继承它。Go 没有「抽象类 + 继承」,我们用「接口 + 多个实现」替代,结构更扁平。

注意 Go 里「工厂接口」其实只是一个 func() Product 类型的别名就能搞定,是否值得专门定义一个 interface 要看:

  • 工厂本身有多个方法或状态 → 用接口。
  • 工厂只是个无状态的创建函数 → 用函数类型更简洁。

下面是函数式版本:

go
package main

import "fmt"

type Notification interface {
	Send(to, msg string) error
}

type EmailNotification struct{}

func (EmailNotification) Send(to, msg string) error {
	fmt.Printf("邮件 -> %s: %s\n", to, msg)
	return nil
}

type SMSNotification struct{}

func (SMSNotification) Send(to, msg string) error {
	fmt.Printf("短信 -> %s: %s\n", to, msg)
	return nil
}

// 工厂就是一个函数类型
type NotificationCreator func() Notification

var registry = map[string]NotificationCreator{
	"email": func() Notification { return EmailNotification{} },
	"sms":   func() Notification { return SMSNotification{} },
}

func notify(kind, to, msg string) {
	creator, ok := registry[kind]
	if !ok {
		fmt.Println("未知类型:", kind)
		return
	}
	creator().Send(to, msg)
}

func main() {
	notify("email", "a@b.com", "hi")
	notify("sms", "10086", "code")
}

三、抽象工厂模式

1. 意图

创建一族相关的对象,而无需指定具体类。抽象工厂和工厂方法的区别:

  • 工厂方法:创建 一个 产品。
  • 抽象工厂:创建 一族 相关产品(多个产品,它们之间有关联)。

典型场景:跨平台 UI 库,在 Windows 上创建 Windows 风格的按钮和输入框,在 Mac 上创建 Mac 风格的按钮和输入框。

2. Go 实现:工厂的工厂

go
package main

import "fmt"

// === 产品族:Button 和 Input ===
type Button interface {
	Render() string
}

type Input interface {
	Render() string
}

// === Windows 风格产品 ===
type WinButton struct{}

func (WinButton) Render() string { return "[Windows 按钮]" }

type WinInput struct{}

func (WinInput) Render() string { return "[Windows 输入框]" }

// === Mac 风格产品 ===
type MacButton struct{}

func (MacButton) Render() string { return "[Mac 按钮]" }

type MacInput struct{}

func (MacInput) Render() string { return "[Mac 输入框]" }

// === 抽象工厂:能同时造 Button 和 Input ===
type UIFactory interface {
	CreateButton() Button
	CreateInput() Input
}

type WinFactory struct{}

func (WinFactory) CreateButton() Button { return WinButton{} }
func (WinFactory) CreateInput() Input   { return WinInput{} }

type MacFactory struct{}

func (MacFactory) CreateButton() Button { return MacButton{} }
func (MacFactory) CreateInput() Input   { return MacInput{} }

// === 根据平台选择工厂(工厂的工厂) ===
func NewUIFactory(platform string) (UIFactory, error) {
	switch platform {
	case "windows":
		return WinFactory{}, nil
	case "mac":
		return MacFactory{}, nil
	default:
		return nil, fmt.Errorf("不支持的平台: %s", platform)
	}
}

// === 应用:用同一个工厂造出一族 UI ===
func renderForm(f UIFactory) {
	fmt.Println("渲染表单:")
	fmt.Println(" ", f.CreateButton().Render())
	fmt.Println(" ", f.CreateInput().Render())
}

func main() {
	for _, p := range []string{"windows", "mac"} {
		f, err := NewUIFactory(p)
		if err != nil {
			fmt.Println(err)
			continue
		}
		fmt.Printf("=== 平台: %s ===\n", p)
		renderForm(f)
	}
}

抽象工厂的关键在于「产品族」——同一个工厂造出的 Button 和 Input 是配套的,不会出现 Windows 按钮配 Mac 输入框这种「混搭」错误。

3. 何时用抽象工厂

抽象工厂是「重武器」,多数场景下过度设计。只有在以下条件 同时 满足时才考虑:

  • 你的系统需要独立于产品的创建方式。
  • 你的系统要配置多个产品族中的一个。
  • 同一族的产品是设计成一起使用的。

否则,用简单工厂 + 注册表就够了。

四、泛型工厂(Go 1.18+)

Go 1.18 引入泛型后,可以写类型安全的通用工厂,避免 interface{} 的类型断言。

1. 泛型工厂函数

go
package main

import "fmt"

// Animal 是所有产品的接口
type Animal interface {
	Speak() string
}

// 泛型工厂:传入「构造函数」类型,返回 Animal
func NewAnimal[T Animal]() Animal {
	var t T
	return t
}

type Dog struct{}

func (Dog) Speak() string { return "汪汪" }

type Cat struct{}

func (Cat) Speak() string { return "喵喵" }

func main() {
	d := NewAnimal[Dog]()
	c := NewAnimal[Cat]()
	fmt.Println(d.Speak())
	fmt.Println(c.Speak())
}

2. 泛型容器工厂

更实用的场景是创建类型安全的容器:

go
package main

import "fmt"

// 泛型栈工厂
func NewStack[T any]() *Stack[T] {
	return &Stack[T]{data: make([]T, 0)}
}

type Stack[T any] struct {
	data []T
}

func (s *Stack[T]) Push(v T) {
	s.data = append(s.data, v)
}

func (s *Stack[T]) Pop() (T, bool) {
	var zero T
	if len(s.data) == 0 {
		return zero, false
	}
	v := s.data[len(s.data)-1]
	s.data = s.data[:len(s.data)-1]
	return v, true
}

func main() {
	ints := NewStack[int]()
	ints.Push(1)
	ints.Push(2)
	v, _ := ints.Pop()
	fmt.Println("int 栈:", v)

	strs := NewStack[string]()
	strs.Push("hi")
	s, _ := strs.Pop()
	fmt.Println("string 栈:", s)
}

泛型工厂的局限:

  • Go 的泛型不支持「泛型方法在方法上单独声明类型参数」,类型参数只能在类型定义或函数签名上声明。
  • 不能像 C++ 那样做模板特化,无法对特定类型给出不同实现。
  • 不能用泛型做 new(T).xxx() 这种需要知道 T 具体方法的操作(除非用接口约束)。

五、工厂模式在标准库中的应用

1. database/sql 的 driver.Register

这是 Go 里最经典的「工厂 + 注册表」组合。database/sql 定义了 driver.Driver 接口(工厂),各数据库驱动在自己的 init() 里调用 sql.Register 把自己注册进去。sql.Open 根据 driver name 找到对应工厂,返回 *sql.DB

下面用一个可独立运行的简化注册表演示同一原理(真实 sql.Register 需要 driver.Driver 接口,依赖较多):

go
package main

import (
	"fmt"
	"sync"
)

// Driver 对应标准库的 driver.Driver
type Driver interface {
	Open(dsn string) (Conn, error)
}

// Conn 对应标准库的 driver.Conn
type Conn interface {
	Close() error
}

// 工厂注册表(包级单例)
var (
	driversMu sync.Mutex
	drivers   = make(map[string]Driver)
)

func Register(name string, d Driver) {
	driversMu.Lock()
	defer driversMu.Unlock()
	drivers[name] = d
}

func Open(name, dsn string) (Conn, error) {
	driversMu.Lock()
	d, ok := drivers[name]
	driversMu.Unlock()
	if !ok {
		return nil, fmt.Errorf("未知驱动: %s", name)
	}
	return d.Open(dsn) // 工厂方法创建连接
}

// 具体驱动 A:内存
type memoryDriver struct{}

func (memoryDriver) Open(dsn string) (Conn, error) {
	fmt.Printf("memoryDriver.Open(%q)\n", dsn)
	return &memoryConn{}, nil
}

type memoryConn struct{}

func (memoryConn) Close() error { return nil }

// 具体驱动 B:文件
type fileDriver struct{}

func (fileDriver) Open(dsn string) (Conn, error) {
	fmt.Printf("fileDriver.Open(%q)\n", dsn)
	return &fileConn{}, nil
}

type fileConn struct{}

func (fileConn) Close() error { return nil }

// init 注册驱动(真实项目里每个驱动在自己的包 init 中注册)
func init() {
	Register("memory", memoryDriver{})
	Register("file", fileDriver{})
}

func main() {
	for _, name := range []string{"memory", "file", "unknown"} {
		conn, err := Open(name, "dsn-xxx")
		if err != nil {
			fmt.Println("错误:", err)
			continue
		}
		fmt.Printf("[%s] 连接成功: %T\n", name, conn)
		conn.Close()
	}
}

说明:标准库 database/sql 的真实实现结构与此完全一致——sql.Registerdriver.Driver 注册到全局 drivers map,sql.Open 按名字查找并返回 *sql.DB。各数据库驱动包在自己的 init() 中调用 sql.Register 完成注册。

2. hash 包的 Hash 接口

hash 包定义了 hash.Hash 接口,具体的 md5.New()sha1.New()sha256.New() 都是工厂函数,返回实现 hash.Hash 的不同实例。这也是「工厂 + 接口」的标准用法:

go
package main

import (
	"crypto/md5"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"hash"
)

func fingerprint(h hash.Hash, data []byte) string {
	h.Write(data)
	return hex.EncodeToString(h.Sum(nil))
}

func main() {
	data := []byte("hello")
	fmt.Println("md5:    ", fingerprint(md5.New(), data))
	fmt.Println("sha256: ", fingerprint(sha256.New(), data))
}

fingerprint 函数依赖 hash.Hash 接口,不关心具体是哪种哈希算法——这就是工厂模式带来的解耦。

3. image 包的解码器注册

image.Decode 根据图片内容自动选择解码器,每个格式(PNG、JPEG、GIF)的包在 init() 里调用 image.RegisterFormat 注册自己。和 database/sql 完全同构。

六、完整实战:插件式支付系统

下面用一个稍完整的例子,把「简单工厂 + 注册表 + 工厂接口」综合起来,模拟一个可扩展的支付系统:

go
package main

import "fmt"

// === 产品接口 ===
type Payment interface {
	Pay(amount float64) (string, error)
}

// === 工厂接口 ===
type PaymentFactory interface {
	Create(config map[string]string) (Payment, error)
}

// === 注册表 ===
var registry = map[string]PaymentFactory{}

func Register(name string, f PaymentFactory) {
	registry[name] = f
}

func NewPayment(name string, cfg map[string]string) (Payment, error) {
	f, ok := registry[name]
	if !ok {
		return nil, fmt.Errorf("未注册的支付方式: %s", name)
	}
	return f.Create(cfg)
}

// === 具体实现:Alipay ===
type Alipay struct {
	AppID string
}

func (a Alipay) Pay(amount float64) (string, error) {
	return fmt.Sprintf("Alipay(appid=%s) 支付 %.2f", a.AppID, amount), nil
}

type AlipayFactory struct{}

func (AlipayFactory) Create(cfg map[string]string) (Payment, error) {
	appid, ok := cfg["app_id"]
	if !ok {
		return nil, fmt.Errorf("alipay 缺少 app_id")
	}
	return Alipay{AppID: appid}, nil
}

// === 具体实现:WechatPay ===
type WechatPay struct {
	MchID string
}

func (w WechatPay) Pay(amount float64) (string, error) {
	return fmt.Sprintf("Wechat(mch=%s) 支付 %.2f", w.MchID, amount), nil
}

type WechatFactory struct{}

func (WechatFactory) Create(cfg map[string]string) (Payment, error) {
	mch, ok := cfg["mch_id"]
	if !ok {
		return nil, fmt.Errorf("wechat 缺少 mch_id")
	}
	return WechatPay{MchID: mch}, nil
}

// === 注册(实际项目里放在各包的 init 中) ===
func init() {
	Register("alipay", AlipayFactory{})
	Register("wechat", WechatFactory{})
}

func main() {
	cases := []struct {
		name string
		cfg  map[string]string
		amt  float64
	}{
		{"alipay", map[string]string{"app_id": "2024"}, 88.8},
		{"wechat", map[string]string{"mch_id": "12345"}, 66.6},
		{"unknown", map[string]string{}, 10},
	}
	for _, c := range cases {
		p, err := NewPayment(c.name, c.cfg)
		if err != nil {
			fmt.Printf("[%s] 错误: %v\n", c.name, err)
			continue
		}
		msg, err := p.Pay(c.amt)
		if err != nil {
			fmt.Printf("[%s] 支付失败: %v\n", c.name, err)
			continue
		}
		fmt.Printf("[%s] %s\n", c.name, msg)
	}
}

这个例子展示了 Go 工厂模式的典型架构:

  • Payment 是产品接口。
  • PaymentFactory 是工厂接口,Create 接收配置参数。
  • registry 是注册表,配合 Register 实现开闭原则。
  • 各支付方式有自己的工厂实现,通过 init 自动注册。
  • NewPayment 是对外的统一入口。

新增一种支付方式时,只需要写一个 XxxFactoryRegister,完全不动现有代码——这就是工厂模式 + 注册表的力量。

七、小结

  • 工厂模式有三种形态:简单工厂(一个函数 + switch)、工厂方法(每产品一个工厂)、抽象工厂(造一族产品)。
  • Go 的 NewXxx 函数天然就是简单工厂,配合 map 注册表可以优雅地实现开闭原则。
  • 工厂方法在 Go 里常用「接口 + 多个实现」或「函数类型」表达,后者更轻量。
  • 抽象工厂适合「产品族」场景(如跨平台 UI),但多数情况是过度设计。
  • Go 1.18+ 泛型让工厂可以类型安全地创建容器和通用对象,但要受限于 Go 泛型的能力(无特化、无泛型方法)。
  • 标准库中 database/sqlhashimage 都是「工厂 + 注册表」的典范,值得反复研读。
  • 实战中「接口 + 注册表 + init 注册」是 Go 工厂模式的黄金组合。

下一篇讲建造者模式,重点介绍 Go 标志性的 Functional Options 模式。