Introduction
The factory pattern is one of the most common and foundational design patterns. For many people, it is also the first pattern they encounter when learning design patterns at all. But in real projects, understanding often stops at a very superficial idea: a factory is just a class that creates objects.
That limited view is exactly where a lot of misuse begins.
Once you spend enough time writing production code, it becomes clear that the essence of the factory pattern has very little to do with whether something is literally named Factory. The important part is not the label. The important part is the separation it creates.
A simple factory example
To get to the core of it, start with a small payment example:
<table> <thead> <tr> <th>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</th>
<th>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)) }</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
</tr>
</tbody>
</table>
At first glance, this looks very ordinary. There is an interface, two concrete implementations, and a NewPayment function that decides which implementation to instantiate.
The real question: why not just new it directly?
Why not write this in main instead?
1 2 3</th>
<th>// 客户直接 "new" payment := &AliPay{} fmt.Println(payment.Pay(100.0))</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
</tr>
</tbody>
</table>
It is shorter. It is simpler. So why bother wrapping object creation in NewPayment?
Because the goal is isolation.
The main function is the place that uses payment functionality. AliPay{} and WechatPay{} are the places that implement payment functionality.
Without a factory, the user of the object directly depends on a concrete implementation.
That creates strong coupling:
- If the construction of
AliPaychanges, for example toNewAliPay(config), thenmainmust change too. - If the caller wants to switch from
AliPaytoWechatPay,mainmust also change.
NewPayment acts as an isolation layer between the code that uses a payment method and the code that knows how a payment method is built.
That means the caller only depends on:
- the abstraction:
IPayment - the creation entry point:
NewPayment
It does not need to care whether NewPayment uses a switch, an if, a config object, or some more complicated construction process internally.
This is the first layer of abstraction: hiding implementation details of object creation.
A version that isn’t called a factory
NewPayment is useful, but it has a serious limitation: it violates the Open/Closed Principle.
If you want to add a new payment type, such as UnionPay, you must modify the switch inside NewPayment.
In small examples that feels harmless. In larger systems it becomes painful. Ideally, adding a new capability should not require editing old logic that already works.
In Go, a more common and often more elegant approach is registration.
<table> <thead> <tr> <th>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</th>
<th>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 函数完全不需要改动! }</th>
</tr>
</thead>
<tbody>
<tr>
<td></td>
<td></td>
</tr>
</tbody>
</table>
This structure should feel familiar.
The Go standard library does something very similar in database/sql:
sql.Register(driverName string, driver driver.Driver)sql.Open(driverName string, dataSourceName string)
sql.Open behaves like the factory entry point. It does not know which database drivers exist—MySQL, PostgreSQL, and so on. It simply looks up the requested driverName in a registry. Each driver package registers itself, typically through init().
That said, implicit registration through init() is not universally loved, and it is not always the right choice. Explicit registration is perfectly acceptable too, as long as the core goal remains the same: decoupling the user from the concrete implementation.
So which one is the factory pattern?
Now there are two approaches:
NewPayment: a centralized factory with a largeswitch, fully aware of every concrete type.GetPaymentplusRegisterPayment: a registry-based factory that does not directly know any concrete implementations; it only knows how to look them up.
Which one counts as the factory pattern?
The answer is: both of them do.
NewPayment is a centralized factory.
GetPayment with RegisterPayment is a registration-based factory.
And that leads to the key point:
The essence of the factory pattern is not the word “factory”
The factory pattern is not defined by a class named Factory, or by a function named NewXxx.
Its essence is this:
take the concrete process of creating an object and separate it from the place where that object is used.
That is an abstraction.
It creates an isolation layer between:
- the consumer of behavior, such as
main - the provider of behavior, such as
AliPay
Whether that isolation is implemented with:
- a
switchstatement - interface plus implementation
- an interface that returns another interface
- a registry
map
is secondary.
Those are implementation techniques. The underlying idea is the important part: abstraction and separation.
If it looks like a registry, provider, manager, or locator...
In real codebases, the mechanism responsible for creation is often not named “factory” at all.
You may see something called a:
- Registry
- Provider
- Manager
- Service Locator
If its core responsibility is to create and return an instance of an abstract type based on some condition, then functionally it is serving the role of a factory.
Whether the word “factory” appears in the name is not what matters.
What actually matters in practice
To put it more plainly: once you abstract away implementation details so the caller no longer needs to care how a concrete object is created, you are already applying the factory pattern.
“Factory” is just one way of describing the act of producing a usable object. The deeper design idea is abstracting construction details so the system becomes more decoupled and more flexible.
That is why this pattern matters so much in everyday engineering work.
A lot of code review, in practice, is really about this very thing: pushing code toward better abstraction boundaries. Getting that boundary right has a huge impact on maintainability.
Once you understand that, it becomes much easier to recognize the factory pattern in places where it is not explicitly named—and to use it more naturally instead of mechanically copying a textbook structure.