Go中单例+工厂模式通过sync.Once实现线程安全单例,接口+工厂函数解耦实现,支持运行时配置与延迟初始化,避免init()硬编码、导出变量等陷阱。
在 Go 语言中,单例 + 工厂模式的组合常用于管理全局、可配置、需统一初始化的核心对象(如数据库连接池、日志实例、配置管理器等)。Go 本身没有类和构造函数,但可通过包级变量 + 惰性初始化 + 接口抽象来优雅实现:单例保证全局唯一,工厂负责按需创建具体类型,同时隐藏初始化细节。
Go 标准库的 sync.Once 是实现单例最推荐的方式——它确保初始化函数仅执行一次,且并发安全,无需手动加锁。
示例:全局日志实例单例
var (
logger *zap.Logger
once sync.Once
)
func GetLogger() *zap.Logger {
once.Do(func() {
l, _ := zap.NewProduction()
logger = l
})
return logger
}
调用 GetLogger() 多次始终返回同一个实例,首次调用时完成初始化。
定义统一接口,让不同环境或配置可返回不同实现(如开发用 console logger,生产用 file logger),再通过工厂函数封装创建逻辑:
type Logger interface {
Info(string, ...zap.Field)
Error(string, ...zap.Field)
}
func NewLogger(env string) Logger {
switch env {
case "dev":
l, _ := zap.NewDevelopment()
return l
case "prod":
l, _ := zap.NewProduction()
return l
default:
return zap.NewNop() // 空实现,避免 panic
}
}
此时单例可基于工厂结果构建:
var (
globalLogger Logger
loggerOnce sync.Once
)
func GetGlobalLogger(env string) Logger {
loggerOnce.Do(func() {
globalLogger = NewLogger(env)
})
return globalLogger
}
实际项目中,配置往往来自命令行、环境变量或配置文件,不能在包初始化阶段硬编码。建议将配置参数传入工厂,并缓存配置+实例绑定关系:
map[string]instance 缓存已创建的实例,键为配置标识(如 "mysql-primary")sync.RWMutex 支持高频读、低频写场景示例简版(无锁优化,适合简单场景):
type DBFactory struct {
instances map[string]*sql.DB
mu sync.RWMutex
}
func (f *DBFactory) GetDB(name string, dsn string) (*sql.DB, error) {
f.mu.RLock()
if db, ok := f.instances[name]; ok {
f.mu.RUn
lock()
return db, nil
}
f.mu.RUnlock()
f.mu.Lock()
defer f.mu.Unlock()
if db, ok := f.instances[name]; ok {
return db, nil
}
db, err := sql.Open("mysql", dsn)
if err != nil {
return nil, err
}
f.instances[name] = db
return db, nil
}
Go 中实现单例+工厂易踩的坑:
var Logger *zap.Logger):破坏封装,外部可随意修改,应只暴露获取函数logger.SetLevel()),而非直接赋值Close() 或 Shutdown() 方法,并由主程序统一调用