创建型设计模式全览:从工厂到原型的系统梳理
为什么是创建型
对象创建并不只是 new 一行代码,它牵扯到依赖注入、外部资源初始化、构造成本与可替换实现。如果创建逻辑散落在业务代码里,很容易出现以下问题:
- 构造流程不可控(重复初始化、连接泄漏、配置不一致)
- 依赖难以替换(测试困难、功能扩展要改大量代码)
- 性能不可预测(昂贵对象频繁创建)
创建型模式的价值在于把“创建”变成一个稳定的扩展点,让对象的构造成本、依赖结构与切换方案可控。
模式清单
- 单例(Singleton)
- 简单工厂(Simple Factory,非 GoF 23)
- 工厂方法(Factory Method)
- 抽象工厂(Abstract Factory)
- 建造者(Builder)
- 原型(Prototype)
单例(Singleton)
适用场景
- 进程内必须共享的组件:配置、日志、连接池、全局缓存、限流器等。
生产案例
- Go 标准库:
log.Default()、http.DefaultClient、http.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/sql:sql.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-go:resolver.Builder.Build、balancer.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-go:kubernetes.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.NewServer、grpc.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.Clone、text/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 用在小对象上,反而降低可读性。
- 原型只做浅拷贝,导致对象共享可变状态。