
通过单例模式封装运行时配置,避免全局变量,实现跨文件安全访问数据库连接等依赖实例。
通过单例模式封装运行时配置,避免全局变量,实现跨文件安全访问数据库连接等依赖实例。
在 Go 应用开发中,常需将 main() 中初始化的关键依赖(如已连接的数据库实例、服务名、日志器等)安全地传递给其他包中的工具函数或业务逻辑,但又不希望引入易出错的全局变量或破坏依赖显式性。此时,单例模式(Singleton Pattern) 是兼顾简洁性与可控性的推荐方案。
核心思路是:定义一个线程安全的、惰性初始化的单例访问器,确保 RuntimeConfig 实例仅被创建一次,且所有调用方通过统一接口获取同一实例。
以下是完整实现示例:
// config/runtime.go
package config
import "sync"
type RuntimeConfig struct {
DB *DatabaseInstance
AppName string
// 其他字段...
}
var (
instance *RuntimeConfig
once sync.Once
)
// GetRuntime 返回全局唯一的 RuntimeConfig 实例。
// 若尚未初始化,调用者需确保先调用 Init()。
func GetRuntime() *RuntimeConfig {
if instance == nil {
panic("RuntimeConfig not initialized. Call config.Init() in main() first.")
}
return instance
}
// Init 显式初始化单例实例(必须在 main() 中调用一次)。
// 参数应由 main() 中已构建好的依赖传入,保持依赖注入的清晰性。
func Init(db *DatabaseInstance, appName string) {
once.Do(func() {
instance = &RuntimeConfig{
DB: db,
AppName: appName,
// 其他字段赋值...
}
})
}
在 main.go 中初始化:
// main.go
func main() {
dbInstance := ConnectToDB(/* ... */)
config.Init(dbInstance, "My app") // ✅ 关键:显式初始化单例
// 启动 HTTP 服务、定时任务等...
http.ListenAndServe(":8080", nil)
}
在任意其他文件中使用(如 elsewhere.go):
// elsewhere.go
package main
import "your-app/config"
func SomeUtilityFuncThatNeedsRuntime(i int) int {
runtime := config.GetRuntime() // ✅ 安全获取已初始化的实例
runtime.DB.Save(runtime.AppName, i) // 示例操作
return i + 1
}
⚠️ 重要注意事项:
- 单例 ≠ 隐式全局状态:
Init()必须由main()显式调用,否则GetRuntime()将 panic,强制开发者关注初始化时机; - 线程安全:
sync.Once保证多 goroutine 并发调用Init()也仅执行一次; - 可测试性:单元测试中可通过
config.Init(...)在测试前重置并注入模拟依赖; - 替代方案提示:对于大型项目,更推荐依赖注入容器(如 wire 或 fx),但单例在中小型应用中更轻量、易理解。
综上,单例模式在此场景下并非“反模式”,而是对 Go 无类、无构造器特性的合理适配——它以最小侵入性解决了跨包共享运行时状态的核心需求,同时守住可维护性与可观测性的底线。










