前言

工厂模式(Factory Pattern)是设计模式中最常见、最基础的模式之一。往往在初学设计模式时,工厂模式是第一个接触到的模式。但在实际工作中,很多人对工厂模式的理解都停留在“有一个工厂类负责创建对象”这样的表面现象上,导致在使用时往往走入误区。

在实际工作之后慢慢积累经验,我发现工厂模式的本质其实并不在于“工厂”这个名字,所以我想带你重新思考一下设计模式的本质。

工厂实现

为了说明工厂模式的本质,我们先来看一个简单的代码例子

比如,我们有一个“支付”场景:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package main

import (
"fmt"
)

// 1. 支付器接口 (产品的抽象)
type IPayment interface {
Pay(amount float64) string
}

// 2. 具体产品A:支付宝
type AliPay struct{}

func (a *AliPay) Pay(amount float64) string {
return fmt.Sprintf("使用支付宝支付了 %.2f 元", amount)
}

// 3. 具体产品B:微信支付
type WechatPay struct{}

func (w *WechatPay) Pay(amount float64) string {
return fmt.Sprintf("使用微信支付了 %.2f 元", amount)
}

// 4. “工厂”函数
// 它根据输入,决定“new”哪个具体的结构体
func NewPayment(payType string) (IPayment, error) {
switch payType {
case "ali":
return &AliPay{}, nil
case "wechat":
return &WechatPay{}, nil
default:
return nil, fmt.Errorf("不支持的支付方式: %s", payType)
}
}

// 5. 客户 (调用方)
func main() {
// 客户代码完全不认识 AliPay 或 WechatPay 结构体
// 它只认识 "IPayment" 接口和 "NewPayment" 工厂
payment, err := NewPayment("ali")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(payment.Pay(100.0))

payment, err = NewPayment("wechat")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(payment.Pay(200.0))
}

灵魂拷问:为什么要这么做?

上面的代码,main 函数(客户)里为什么不直接这样写?

1
2
3
// 客户直接 "new"
payment := &AliPay{}
fmt.Println(payment.Pay(100.0))

这样不是更简单吗?为什么非要搞一个 NewPayment 函数绕一下?

答案是:为了“隔离”。

main 函数是“使用”支付功能的地方,而 AliPay{}WechatPay{} 是“实现”支付功能的地方。

在没有工厂的情况下,“使用者”main 强依赖于“实现者”AliPay

  • 如果 AliPay 的创建方式变了(比如 NewAliPay(config)),main 函数就必须修改。
  • 如果 main 函数想换成 WechatPaymain 函数也必须修改。

NewPayment 函数,就是那个“隔离层”。

使用工厂的好处是:
“使用者”(main)和“具体实现”(AliPay)解耦了。
main 只依赖“抽象”的 IPayment 接口和“工厂” NewPayment

至于 NewPayment 内部是用 switch 还是 if,是返回 &AliPay{} 还是 &AliPay{config: ...}main 根本不关心。

这,就是“抽象实现细节”的第一层。

一个不叫“工厂”的实现

NewPayment 函数虽然好,但它有个致命缺陷:违反了“开闭原则”

如果我们要增加一种“银联支付”(UnionPay),我们必须去修改 NewPayment 函数的 switch 逻辑。

这在大型项目中往往会变得更加麻烦。我们希望的是,增加新功能时,不修改老代码。

我们来看一个在 Go 中更常见、更优雅的实现方式 —— “注册”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package main

import (
"fmt"
)

// --- 支付接口和具体实现 (这部分不变) ---
type IPayment interface {
Pay(amount float64) string
}
type AliPay struct{}
func (a *AliPay) Pay(amount float64) string { /* ... */ return "支付宝支付" }
type WechatPay struct{}
func (w *WechatPay) Pay(amount float64) string { /* ... */ return "微信支付" }

// --- 我们不再叫 "Factory" ---

// 1. 定义一个“创建者”的函数原型
type PaymentCreator func() IPayment

// 2. 一个全局的“注册表”
var paymentRegistry = make(map[string]PaymentCreator)

// 3. 注册函数:允许外部注册新的支付方式
func RegisterPayment(payType string, creator PaymentCreator) {
if _, ok := paymentRegistry[payType]; ok {
// 警告:重复注册
}
paymentRegistry[payType] = creator
}

// 4. 获取函数:从注册表中获取实例
// 这个函数就是我们新的“工厂”,但它不叫“工厂”
func GetPayment(payType string) (IPayment, error) {
creator, ok := paymentRegistry[payType]
if !ok {
return nil, fmt.Errorf("不支持的支付方式: %s", payType)
}
// "创建" 的动作在这里发生
return creator(), nil
}

// --- 在不同的包中初始化 (模拟插件化) ---
// (在实际项目中,这会在 ali_pay.go 和 wechat_pay.go 的 init() 中完成)
func init() {
RegisterPayment("ali", func() IPayment { return &AliPay{} })
RegisterPayment("wechat", func() IPayment { return &WechatPay{} })
}

// --- 客户代码 ---
func main() {
// 客户现在只依赖 GetPayment
payment, err := GetPayment("ali")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(payment.Pay(100.0))

// 思考:如果我们想增加“银联支付”?
// 我们只需要新建 union_pay.go,并在其 init() 中调用 RegisterPayment
// GetPayment 和 main 函数完全不需要改动!
}

这个实现是不是很眼熟?

Go语言标准库 database/sql 就是这么干的。

  • sql.Register(driverName string, driver driver.Driver)
  • sql.Open(driverName string, dataSourceName string)

sql.Open 就是那个“工厂”函数,它根本不知道有哪些数据库驱动(MySQL, PostgreSQL…),它只管去注册表里查 driverName。而各个驱动包通过 init() 函数把自己注册进去。

注意并不是所有的情况都适合使用 init 方法去隐式注册(有的人不喜欢),那么显示注册也是可以的,只要能达到“使用者”和“实现者”解耦的目的即可。

我不叫“工厂”,就不是工厂了吗?

现在,我们有了两个实现:

  1. NewPayment:一个巨大的 switch,它知道所有具体实现。
  2. GetPayment:一个 map 和一个查找函数,它不知道任何具体实现,它只知道一个“注册表”。

哪个是“工厂模式”?

答案是:它们都是。

NewPayment 是一个“集中式”的工厂。
GetPayment (以及 RegisterPayment) 是一个“注册式”的工厂。

这引出了我们最终的结论:

工厂模式的本质,不是那个叫 Factory 的类或那个叫 NewXxx 的函数。

工厂模式的本质是:将“创建对象的具体过程”从“使用对象的地方”中剥离出来。

它是一种抽象

它在“使用者”(如 main 函数)和“实现者”(如 AliPay 结构体)之间,建立了一个隔离层

至于是用 switch(简单工厂)、用“接口+实现”(工厂方法)、用“接口返回接口”(抽象工厂),还是用 map(注册器)来实现的……

这都不重要。

这些只是“术”(实现方式),而“道”(本质)是抽象隔离

所以,下次当你在代码里看到一个“注册中心”(Registry)、一个“提供者”(Provider)、一个“管理器”(Manager)或一个“服务定位器”(Service Locator)时,如果它的核心职责是根据某些条件创建并返回一个抽象接口的实例,那么,它就是工厂模式。

它叫不叫“工厂”,真的无所谓。

总结

其实再说的直白一点,只要你抽象了实现细节,让使用者不需要关心具体实现类的创建过程,就是在使用工厂模式了。而“工厂”只是说你抽象之后是一个创建“产品”的说法而已。

所以,工厂模式的核心思想是抽象实现细节,而这个思想是让你的代码更加解耦灵活的方法之一。也是程序设计中非常重要的一个原则。

在实际中我做过大量的 Code Review 其实都是在做抽象这一件事,这个优化真的很重要,所以希望通过这篇文章能让你对工厂模式有一个新的认识。