创建型设计模式全览:从工厂到原型的系统梳理

创建型设计模式全览:从工厂到原型的系统梳理

为什么是创建型

对象创建并不只是 new 一行代码,它牵扯到依赖注入、外部资源初始化、构造成本与可替换实现。如果创建逻辑散落在业务代码里,很容易出现以下问题:

  • 构造流程不可控(重复初始化、连接泄漏、配置不一致)
  • 依赖难以替换(测试困难、功能扩展要改大量代码)
  • 性能不可预测(昂贵对象频繁创建)

创建型模式的价值在于把“创建”变成一个稳定的扩展点,让对象的构造成本、依赖结构与切换方案可控。

模式清单

  • 单例(Singleton)
  • 简单工厂(Simple Factory,非 GoF 23)
  • 工厂方法(Factory Method)
  • 抽象工厂(Abstract Factory)
  • 建造者(Builder)
  • 原型(Prototype)

单例(Singleton)

适用场景

  • 进程内必须共享的组件:配置、日志、连接池、全局缓存、限流器等。

生产案例

  • Go 标准库:log.Default()http.DefaultClienthttp.DefaultServeMux 是全局默认实例。
  • 业务中常见的 *sql.DB 作为全局连接池(并发安全且需复用)。

Go 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package config

import "sync"

type Config struct {
Endpoint string
}

var (
instance *Config
once sync.Once
)

func GetConfig() *Config {
once.Do(func() {
instance = &Config{Endpoint: "https://api.example.com"}
})
return instance
}

注意点

  • 单例是隐式全局状态,会加重耦合;尽量通过接口与依赖注入隔离。
  • 并发环境要确保初始化安全。

简单工厂(Simple Factory,非 GoF 23)

适用场景

  • 产品类型有限,创建逻辑集中即可。

生产案例

  • database/sqlsql.Open(driverName, dsn) 通过注册表选择具体驱动(MySQL/Postgres)。
  • image.Decode:根据文件头与已注册解码器返回不同实现(PNG/JPEG/GIF)。

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
package payment

import "fmt"

type Payment interface {
Pay(amount int) error
}

type WechatPay struct{}
func (WechatPay) Pay(amount int) error { return nil }

type Alipay struct{}
func (Alipay) Pay(amount int) error { return nil }

func NewPayment(method string) (Payment, error) {
switch method {
case "wechat":
return WechatPay{}, nil
case "alipay":
return Alipay{}, nil
default:
return nil, fmt.Errorf("unsupported method: %s", method)
}
}

注意点

  • 新增产品必须修改工厂函数,违反开闭原则。
  • 适合规模小、扩展少的场景。

工厂方法(Factory Method)

适用场景

  • 产品扩展频繁,希望通过新增实现完成扩展而不是修改工厂逻辑。

生产案例

  • database/sql/driver.Driver.Open:每个驱动实现自己的连接创建方式,返回 driver.Conn
  • grpc-goresolver.Builder.Buildbalancer.Builder.Build 用于创建解析器与负载均衡器实例,插件化扩展。

Go 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package logger

type Logger interface {
Log(msg string)
}

type Factory interface {
Create() Logger
}

type FileFactory struct {
Path string
}

func (f FileFactory) Create() Logger {
return &FileLogger{Path: f.Path}
}

注意点

  • 会引入多个工厂实现类,但换来更好的扩展性与隔离性。

抽象工厂(Abstract Factory)

适用场景

  • 需要成套替换实现(产品族切换),例如不同数据库、不同缓存方案或不同平台适配层。

生产案例

  • database/sql:不同 driver.Driver 产出同一族组件(Conn/Stmt/Tx),切换驱动即可整体替换。
  • k8s.io/client-gokubernetes.NewForConfig 返回 Clientset,内部多个 API 客户端共享同一传输与序列化配置。

Go 实现

1
2
3
4
5
6
7
8
9
10
11
12
type UserRepo interface{ Find(id int) (*User, error) }
type OrderRepo interface{ Find(id int) (*Order, error) }

type RepoFactory interface {
NewUserRepo() UserRepo
NewOrderRepo() OrderRepo
}

type MySQLFactory struct{}

func (MySQLFactory) NewUserRepo() UserRepo { return &MySQLUserRepo{} }
func (MySQLFactory) NewOrderRepo() OrderRepo { return &MySQLOrderRepo{} }

注意点

  • 产品族一旦固定,新增“产品种类”会牵涉接口变更。

建造者(Builder)

适用场景

  • 构造参数多、可选项多,或构建过程需要分步校验。

生产案例

  • grpc.NewServergrpc.Dial 通过一组 Option 组装参数。
  • zap.NewProductionConfig().Build() 通过配置对象构建 Logger。

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
package server

import "time"

type Server struct {
addr string
timeout time.Duration
maxConns int
}

type Option func(*Server)

func WithAddr(addr string) Option {
return func(s *Server) { s.addr = addr }
}

func WithTimeout(d time.Duration) Option {
return func(s *Server) { s.timeout = d }
}

func WithMaxConns(n int) Option {
return func(s *Server) { s.maxConns = n }
}

func NewServer(opts ...Option) *Server {
s := &Server{timeout: 5 * time.Second}
for _, opt := range opts {
opt(s)
}
return s
}

注意点

  • Builder 适合复杂构造;简单对象不要过度设计。

原型(Prototype)

适用场景

  • 初始化成本高、对象结构稳定但内容差异有限。

生产案例

  • google.golang.org/protobuf/proto.Clone 用于复制 protobuf 消息。
  • http.Request.Clonetext/template.Template.Clone 支持对象复制。
  • Kubernetes API 类型自动生成 DeepCopy 以安全复制对象。

Go 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
type Template struct {
Name string
Tags []string
Meta map[string]string
}

func (t *Template) Clone() *Template {
tags := make([]string, len(t.Tags))
copy(tags, t.Tags)

meta := make(map[string]string, len(t.Meta))
for k, v := range t.Meta {
meta[k] = v
}

return &Template{
Name: t.Name,
Tags: tags,
Meta: meta,
}
}

注意点

  • 必须搞清楚浅拷贝与深拷贝的差异,切片/映射尤其容易出坑。

选型速记

  • 创建逻辑需要集中:简单工厂。
  • 扩展频繁且希望新增类而不是改逻辑:工厂方法。
  • 要切换一整套实现(产品族):抽象工厂。
  • 构造参数多或构建过程复杂:建造者/函数式选项。
  • 创建成本高且对象结构稳定:原型。

常见坑

  • 单例滥用成全局状态容器,导致依赖隐藏、测试困难。
  • 工厂层级过多,维护成本超过收益。
  • Builder 用在小对象上,反而降低可读性。
  • 原型只做浅拷贝,导致对象共享可变状态。