Go 语言单例设计模式

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全栈开发完整课程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值