Что это такое
Паттерн Singleton гарантирует, что у типа есть ровно один экземпляр во всей программе, и даёт всем одну и ту же точку доступа к нему. Типичные синглтоны: конфигурация, пул соединений, логгер.
В Go для этого есть sync.Once: код инициализации внутри once.Do() выполняется ровно один раз, даже если много горутин обращаются к нему одновременно.
Пример: одна конфигурация для всех
Конфигурация загружается при первом вызове GetConfig() и больше никогда. Каждый вызывающий получает один и тот же указатель.
Загрузка один раз с помощью sync.Once
Строка «загружаем конфигурацию» печатается только один раз, хотя GetConfig() вызывается дважды, — а a == b доказывает, что оба вызывающих держат один и тот же экземпляр.
Вывод: программа на Go и захваченный вывод
package main
import (
"fmt"
"sync"
)
type Config struct {
AppName string
}
var (
once sync.Once
instance *Config
)
func GetConfig() *Config {
once.Do(func() {
fmt.Println("загружаем конфигурацию (происходит один раз)")
instance = &Config{AppName: "PromptCookbook"}
})
return instance
}
func main() {
a := GetConfig()
b := GetConfig()
fmt.Println("приложение:", a.AppName)
fmt.Println("тот же экземпляр:", a == b)
}
Когда его использовать
Используйте, когда чего-то действительно должно быть ровно одно: одна загруженная конфигурация, один пул соединений, один кэш — и когда его создание дорого или дубликаты будут конфликтовать между собой.
Откажитесь, если значение можно просто создать в main() и передать тем, кому оно нужно. Явная передача зависимостей понятнее глобального доступа — синглтон это последнее средство, а не первое.
Плюсы и минусы
Плюсы: гарантированно единственный экземпляр; ленивое создание при первом обращении; безопасность при работе многих горутин благодаря sync.Once.
Минусы: это замаскированная глобальная переменная — скрытые зависимости усложняют чтение кода; в тестах его непросто подменить заглушкой; злоупотребление приводит к запутанным программам.