Ответ
Singleton (Одиночка) — это порождающий паттерн проектирования, который гарантирует, что у класса есть только один экземпляр, и предоставляет глобальную точку доступа к этому экземпляру.
В Go идиоматичная реализация Singleton достигается с помощью пакета sync и его типа Once, который обеспечивает потокобезопасную инициализацию ровно один раз.
Пример на Go (потокобезопасный):
package main
import (
"fmt"
"sync"
)
// Структура, которую мы хотим сделать синглтоном
// (например, подключение к БД или конфигурация)
type DatabaseConnection struct {
connectionString string
}
func (db *DatabaseConnection) Query(q string) {
fmt.Printf("Executing query '%s' using connection '%s'n", q, db.connectionString)
}
var (
instance *DatabaseConnection
once sync.Once
)
// GetInstance предоставляет глобальную точку доступа
func GetInstance() *DatabaseConnection {
// once.Do выполнит переданную функцию только один раз
// при первом вызове GetInstance.
once.Do(func() {
fmt.Println("Creating database connection...")
instance = &DatabaseConnection{connectionString: "user:pass@tcp(127.0.0.1:3306)/db"}
})
return instance
}
func main() {
// Все вызовы будут возвращать один и тот же экземпляр
conn1 := GetInstance()
conn1.Query("SELECT * FROM users")
conn2 := GetInstance()
conn2.Query("SELECT * FROM products")
}
// Вывод:
// Creating database connection...
// Executing query 'SELECT * FROM users' using connection 'user:pass@tcp(127.0.0.1:3306)/db'
// Executing query 'SELECT * FROM products' using connection 'user:pass@tcp(127.0.0.1:3306)/db'
Преимущества:
- Гарантия единственного экземпляра: Полезно для ресурсов, которые должны быть уникальными (пул соединений, логгер, конфигурация).
- Ленивая инициализация (Lazy Initialization): Ресурс создается только тогда, когда он действительно нужен в первый раз.
- Глобальный доступ: Легко получить доступ к экземпляру из любой части программы.
Недостатки и критика:
Singleton часто считают анти-паттерном в современной разработке:
- Глобальное состояние: Вносит в программу глобальное состояние, что затрудняет отслеживание зависимостей и отладку.
- Сложность тестирования: Модули, использующие Singleton, становятся тесно связанными с ним. Сложно подменить синглтон на mock-объект в unit-тестах.
- Нарушение принципа единственной ответственности (SRP): Класс-синглтон отвечает не только за свою бизнес-логику, но и за контроль количества своих экземпляров.
- Скрытые зависимости: Зависимость от синглтона не видна в сигнатуре функции или конструкторе, что делает код менее прозрачным.
Альтернатива: В большинстве случаев предпочтительнее использовать Внедрение зависимостей (Dependency Injection), так как это делает код более модульным, тестируемым и гибким.
Ответ 18+ 🔞
Вот, представляешь, есть такая штука — Singleton, или по-нашему Одиночка. Это такой паттерн, который смотрит на тебя и говорит: «Слышь, дружок, экземпляр этого класса будет один, и только один, на весь проект, и точка». А потом ещё любезно предлагает глобальную точку доступа к этому самому экземпляру, чтобы ты не бегал по коду как угорелый.
В Go, если делать по-человечески, это реализуется через пакет sync и его штуку Once, которая гарантирует, что инициализация произойдёт один раз и без гонок, даже если двадцать потоков одновременно налетят.
Пример на Go (чтобы не словить race condition):
package main
import (
"fmt"
"sync"
)
// Допустим, у нас есть какая-то важная хрень, которая должна быть в единственном экземпляре.
// Ну, типа подключение к базе данных или конфиг.
type DatabaseConnection struct {
connectionString string
}
func (db *DatabaseConnection) Query(q string) {
fmt.Printf("Executing query '%s' using connection '%s'n", q, db.connectionString)
}
var (
instance *DatabaseConnection
once sync.Once
)
// GetInstance — это та самая дверь, в которую все стучатся за экземпляром.
func GetInstance() *DatabaseConnection {
// once.Do — это магия. Функция внутри выполнится ровно один раз, даже если вызовов будет овердохуища.
once.Do(func() {
fmt.Println("Creating database connection...")
instance = &DatabaseConnection{connectionString: "user:pass@tcp(127.0.0.1:3306)/db"}
})
return instance
}
func main() {
// И неважно, сколько раз ты его вызываешь — вернётся один и тот же объект.
conn1 := GetInstance()
conn1.Query("SELECT * FROM users")
conn2 := GetInstance()
conn2.Query("SELECT * FROM products")
}
// Вывод:
// Creating database connection...
// Executing query 'SELECT * FROM users' using connection 'user:pass@tcp(127.0.0.1:3306)/db'
// Executing query 'SELECT * FROM products' using connection 'user:pass@tcp(127.0.0.1:3306)/db'
Что тут хорошего, спросишь?
- Один экземпляр и ни хуя больше: Идеально для вещей, которые должны быть уникальными — типа того же пула соединений, логгера или конфигурации. Не будет ситуации, что у тебя два логгера пишут в разные файлы и потом разбирайся, чёрт ногу сломит.
- Ленивая инициализация: Ресурс создастся только тогда, когда он впервые понадобится. Не будет грузить систему на старте просто так.
- Глобальный доступ: Достал экземпляр из любой дыры в коде — и порядок.
А теперь про недостатки, потому что без них никуда:
Singleton частенько называют анти-паттерном, и не просто так, ёпта.
- Глобальное состояние: Он вносит в программу глобальное состояние, а это, блядь, как мина замедленного действия. Отслеживать зависимости и отлаживать такой код — просто пиздец.
- Тестирование превращается в ад: Модули, которые юзают синглтон, становятся с ним неразлучными. Подменить его на mock в unit-тестах — задача не для слабонервных.
- Нарушает принцип единственной ответственности: Класс теперь отвечает не только за свою прямую работу, но и за то, чтобы экземпляр был один. Это уже перебор.
- Скрытые зависимости: Зависимость от синглтона не видна в параметрах функции или конструкторе. Смотришь на сигнатуру — вроде чисто, а внутри него сидит и тихо управляет твоим кодом. Подло, да?
Что делать вместо этого? В большинстве случаев, честно, лучше использовать Внедрение зависимостей (Dependency Injection). Да, придётся немного больше писать, зато код станет модульным, тестируемым и гибким. А синглтон... ну, иногда его можно применить, но с оглядкой, как спичку в пороховом погребе.