Go 语言单例设计模式
🏗 什么是单例模式?
单例(Singleton)是一种创建型设计模式, 它的目标是:一个类只有一个实例,并且提供全局访问点。
用白话说,就是:
整个程序中,这个对象只能创建一次,多次调用都用同一个。
典型场景:配置管理、日志记录、数据库连接池等。
🏹 Go 中实现单例的特点
因为 Go 没有像 Java 那样的类和构造器, 它主要用 包级变量 + sync.Once + init 函数 来实现单例。
Go 关键点: ✅ 包级变量 ✅ sync.Once 保证只初始化一次(线程安全) ✅ 提供对外公开的获取实例方法
🌟 单例模式完整代码讲解
我们一步步来看,最后给你拼成完整例子。
① 定义结构体(要单例的对象)
假设我们要实现一个 Logger 日志记录器。
type Logger struct {
logLevel string
}
这个 Logger 是我们只希望存在一个的对象。
② 定义包级变量
var instance *Logger
注意:
-
用
var定义全局变量instance -
最开始它是
nil
③ 使用 sync.Once 保证只初始化一次
Go 提供了 sync.Once,它的 Do 方法能保证:
无论多少 goroutine 调用,函数只执行一次。
import "sync" var once sync.Once
④ 提供获取单例实例的方法
我们写一个 GetInstance() 方法, 外部代码都通过它来获取单例。
func GetInstance() *Logger {
once.Do(func() {
instance = &Logger{
logLevel: "INFO", // 默认日志级别
}
})
return instance
}
这里的关键:
-
第一次调用时,
once.Do中的函数会运行,创建Logger实例。 -
后面再调用
GetInstance(),once.Do不会再跑,直接返回之前创建的instance。
⑤ 测试一下单例
写个 main 函数测试看看:
package main
import (
"fmt"
"singleton"
)
func main() {
logger1 := singleton.GetInstance()
logger2 := singleton.GetInstance()
if logger1 == logger2 {
fmt.Println("是同一个实例")
} else {
fmt.Println("不是同一个实例")
}
logger1.logLevel = "DEBUG"
fmt.Println("logger2 的 logLevel:", logger2.logLevel) // 应该输出 DEBUG
}
输出:
是同一个实例 logger2 的 logLevel: DEBUG
这证明:
-
logger1 和 logger2 指向同一个内存对象。
-
改变一个的属性,另一个也能看到。
📦 完整代码(带 package)
文件结构:
/singleton singleton.go main.go
singleton/singleton.go
package singleton
import "sync"
type Logger struct {
logLevel string
}
var instance *Logger
var once sync.Once
func GetInstance() *Logger {
once.Do(func() {
instance = &Logger{
logLevel: "INFO",
}
})
return instance
}
main.go
package main
import (
"fmt"
"singleton"
)
func main() {
logger1 := singleton.GetInstance()
logger2 := singleton.GetInstance()
if logger1 == logger2 {
fmt.Println("是同一个实例")
} else {
fmt.Println("不是同一个实例")
}
logger1.logLevel = "DEBUG"
fmt.Println("logger2 的 logLevel:", logger2.logLevel)
}
🛡 为什么要用 sync.Once?
很多人会问:
为什么不用 init() 直接初始化?
因为:
-
init() 是包加载时调用的,不是懒加载,可能浪费资源。
-
多线程(goroutine)环境下,如果手动加锁实现,容易写错。
-
sync.Once 是 Go 专门为「只执行一次」设计的,简单可靠、线程安全。
📌 单例模式的优缺点
| ✅ 优点 | ⚠️ 缺点 |
|---|---|
| 只有一个实例,节省资源 | 不容易扩展为多实例 |
| 提供全局访问点,方便共享 | 全局状态可能导致隐藏的依赖关系 |
| 延迟加载(懒汉式)节省初始化开销 | 单例隐藏在包里,测试时不好 mock |
👉 立即点击链接,开启你的全栈开发之路:Golang全栈开发完整课程
577

被折叠的 条评论
为什么被折叠?



