Appearance
工厂模式
工厂模式是创建型模式中最常见的一族,包含三种形态:简单工厂、工厂方法、抽象工厂。在 Java 里,这三者的区分很明确,往往需要定义多个抽象类和接口。在 Go 里,由于「NewXxx 函数 + 接口」已经是默认写法,工厂模式常常以更轻量的形式出现。本章我们逐一讲解三种形态,并介绍 Go 1.18+ 泛型如何让工厂更类型安全。
一、简单工厂模式
1. 意图
定义一个工厂函数,根据入参返回不同的具体类型。客户端不需要知道具体类型的构造细节,只需要告诉工厂「我要哪种」。
2. Go 实现:NewXxx + switch
Go 的简单工厂就是一个带 switch 的 NewXxx 函数:
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/sql、image 包都是),它让简单工厂具备了「开闭原则」特性:新增产品不需要改工厂代码,只需要 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.Register把driver.Driver注册到全局driversmap,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是对外的统一入口。
新增一种支付方式时,只需要写一个 XxxFactory 并 Register,完全不动现有代码——这就是工厂模式 + 注册表的力量。
七、小结
- 工厂模式有三种形态:简单工厂(一个函数 + switch)、工厂方法(每产品一个工厂)、抽象工厂(造一族产品)。
- Go 的
NewXxx函数天然就是简单工厂,配合map注册表可以优雅地实现开闭原则。 - 工厂方法在 Go 里常用「接口 + 多个实现」或「函数类型」表达,后者更轻量。
- 抽象工厂适合「产品族」场景(如跨平台 UI),但多数情况是过度设计。
- Go 1.18+ 泛型让工厂可以类型安全地创建容器和通用对象,但要受限于 Go 泛型的能力(无特化、无泛型方法)。
- 标准库中
database/sql、hash、image都是「工厂 + 注册表」的典范,值得反复研读。 - 实战中「接口 + 注册表 + init 注册」是 Go 工厂模式的黄金组合。
下一篇讲建造者模式,重点介绍 Go 标志性的 Functional Options 模式。