我们来详细介绍 Go 语言中的两个经典创建型设计模式 :单例模式(Singleton Pattern) 和 工厂模式(Factory Pattern)。
这两种模式在实际开发中非常常见,尤其是在构建配置管理、数据库连接池、日志系统等场景中。
一、单例模式(Singleton Pattern)
🎯 目的:
确保一个类在整个程序生命周期中只有一个实例,并提供一个全局访问点。
类似于"全局唯一对象",比如:数据库连接池、配置管理器、日志记录器。
✅ Go 实现方式(推荐:sync.Once
+ 懒加载)
go
package main
import (
"fmt"
"sync"
)
// ConfigManager 单例结构体
type ConfigManager struct {
config map[string]string
}
// 全局变量(私有)
var (
instance *ConfigManager
once sync.Once
)
// GetInstance 返回唯一的 ConfigManager 实例
func GetInstance() *ConfigManager {
once.Do(func() {
instance = &ConfigManager{
config: make(map[string]string),
}
fmt.Println("ConfigManager 初始化")
})
return instance
}
// Set 设置配置
func (c *ConfigManager) Set(key, value string) {
c.config[key] = value
}
// Get 获取配置
func (c *ConfigManager) Get(key string) string {
return c.config[key]
}
🔧 使用示例:
go
func main() {
// 获取单例实例
cfg1 := GetInstance()
cfg1.Set("host", "localhost")
// 再次获取,应是同一个实例
cfg2 := GetInstance()
fmt.Println(cfg2.Get("host")) // 输出: localhost
// 验证是否为同一实例
fmt.Printf("cfg1 == cfg2: %t\n", cfg1 == cfg2) // true
}
✅ 优点:
- 线程安全(
sync.Once
保证只初始化一次) - 懒加载(第一次调用才创建)
- 全局唯一
⚠️ 注意事项:
- 不要滥用单例(可能导致测试困难、耦合度高)
- Go 中更推荐依赖注入(DI),但在某些场景下单例仍实用
二、工厂模式(Factory Pattern)
🎯 目的:
定义一个用于创建对象的接口,但让子类决定实例化哪一个类 。Go 中没有"继承",所以通常使用 函数返回接口 来实现。
工厂模式的核心是:解耦对象的创建与使用。
1. 简单工厂模式(Simple Factory)
场景:根据类型创建不同的支付方式
go
package main
import "fmt"
// Payment 支付接口
type Payment interface {
Pay(amount float64) string
}
// Alipay 支付宝
type Alipay struct{}
func (a *Alipay) Pay(amount float64) string {
return fmt.Sprintf("支付宝支付 %.2f 元", amount)
}
// WeChatPay 微信支付
type WeChatPay struct{}
func (w *WeChatPay) Pay(amount float64) string {
return fmt.Sprintf("微信支付 %.2f 元", amount)
}
// UnionPay 银联支付
type UnionPay struct{}
func (u *UnionPay) Pay(amount float64) string {
return fmt.Sprintf("银联支付 %.2f 元", amount)
}
// PaymentFactory 工厂函数
func NewPayment(method string) Payment {
switch method {
case "alipay":
return &Alipay{}
case "wechat":
return &WeChatPay{}
case "union":
return &UnionPay{}
default:
panic("不支持的支付方式")
}
}
🔧 使用示例:
go
func main() {
payment := NewPayment("alipay")
result := payment.Pay(99.9)
fmt.Println(result) // 支付宝支付 99.90 元
payment = NewPayment("wechat")
result = payment.Pay(50.0)
fmt.Println(result) // 微信支付 50.00 元
}
✅ 优点:调用方无需知道具体类型,只需传入字符串即可获得对应实现。
2. 抽象工厂模式(Abstract Factory)【可选扩展】
如果需要创建一系列相关或依赖对象(如:主题工厂创建按钮+文本框),可以使用抽象工厂。
go
type Button interface {
Click()
}
type TextBox interface {
Input(text string)
}
type UIFactory interface {
CreateButton() Button
CreateTextBox() TextBox
}
type MacFactory struct{}
func (m *MacFactory) CreateButton() Button { return &MacButton{} }
func (m *MacFactory) CreateTextBox() TextBox { return &MacTextBox{} }
type WinFactory struct{}
func (w *WinFactory) CreateButton() Button { return &WinButton{} }
func (w *WinFactory) CreateTextBox() TextBox { return &WinTextBox{} }
三、单例 vs 工厂模式对比
特性 | 单例模式 | 工厂模式 |
---|---|---|
类型 | 创建型 | 创建型 |
目的 | 确保唯一实例 | 解耦对象创建过程 |
核心 | 全局唯一 | 多态创建 |
适用场景 | 配置管理、连接池 | 支付系统、插件系统、消息队列选择 |
Go 实现 | sync.Once + 全局变量 |
函数返回接口 |
是否线程安全 | 是(需正确实现) | 是(工厂本身无状态) |
四、实际应用场景
模式 | 应用场景 |
---|---|
单例模式 | - 数据库连接池 - 日志记录器 - 配置中心 - 缓存实例(如全局 Redis 客户端) |
工厂模式 | - 支付网关选择 - 消息队列驱动(Kafka/RabbitMQ) - 存储后端(本地/云存储) - API 客户端版本切换 |
五、注意事项(Go 特性)
- Go 没有构造函数 :用
NewXXX()
函数代替。 - 优先使用接口:工厂返回接口而非具体类型。
- 避免全局状态滥用:单例虽方便,但会增加耦合,影响测试。
- 推荐依赖注入(DI):比单例更灵活(如使用 Wire、Dig 等框架)。
✅ 总结
🔑 单例模式口诀:
"一生一世一双人 "
全局唯一,懒加载,
sync.Once
保安全。
🔑 工厂模式口诀:
"你告诉我类型,我给你对象 "
解耦创建逻辑,扩展无忧。
掌握这两个模式,你就能写出高内聚、低耦合、易扩展的 Go 程序!它们是构建大型系统的基础组件。