![Одинак](/images/patterns/cards/singleton-mini.png?id=914e1565dfdf15f240e766163bd303ec)
Одинак на Go
Одинак — це породжуючий патерн, який гарантує існування тільки одного об’єкта певного класу, а також дозволяє дістатися цього об’єкта з будь-якого місця програми.
Одинак має такі ж переваги та недоліки, що і глобальні змінні. Його неймовірно зручно використовувати, але він порушує модульність вашого коду.
Ви не зможете просто взяти і використовувати клас, залежний від одинака, в іншій програмі. Для цього доведеться емулювати там присутність одинака. Найчастіше ця проблема проявляється при написанні юніт-тестів.
Концептуальний приклад
Зазвичай, екземпляр одинака створюється під час початкової ініціалізації структури. Для цього, ми визначаємо для структури метод getInstance
. Цей метод буде створювати і повертати екземпляри одинака. Після створення першого екземпляра, під час кожного виклику методу getInstance
буде повертатися саме він.
А що стосовно потоків goroutine? Структура одинака повинна повертати один і той же екземпляр у випадках, коли різні потоки намагаються отримати доступ до цього екземпляра. З цієї причини можна легко помилитися і неправильно реалізувати патерн Одинак. Приклад нижче показує, як правильно створити Одинака.
Деякі важливі деталі, про які потрібно пам’ятати:
-
На початку потрібна
nil
-перевірка, за допомогою неї ми переконуємося, що перший примірникsingleInstance
— порожній. Завдяки цьому ми можемо уникнути ресурсномістких операцій блокування під час кожного викликуgetInstance
. Якщо ця перевірка не пройдена, тоді полеsingleInstance
вже заповнено. -
Структура
singleInstance
створюється всередині блокування. -
Після блокування використовується ще одна
nil
-перевірка. У випадках, коли першу перевірку проходить понад одного потоку, друга забезпечує створення екземпляра одинака лише одним потоком. Інакше всі потоки створювали б свої екземпляри структури одинака.
single.go: Одинак
package main
import (
"fmt"
"sync"
)
var lock = &sync.Mutex{}
type single struct {
}
var singleInstance *single
func getInstance() *single {
if singleInstance == nil {
lock.Lock()
defer lock.Unlock()
if singleInstance == nil {
fmt.Println("Creating single instance now.")
singleInstance = &single{}
} else {
fmt.Println("Single instance already created.")
}
} else {
fmt.Println("Single instance already created.")
}
return singleInstance
}
main.go: Клієнтський код
package main
import (
"fmt"
)
func main() {
for i := 0; i < 30; i++ {
go getInstance()
}
// Scanln is similar to Scan, but stops scanning at a newline and
// after the final item there must be a newline or EOF.
fmt.Scanln()
}
output.txt: Результат виконання
Creating single instance now.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Single instance already created.
Інший приклад
Є і інші методи створення екземпляра одинака в Go:
- Функція
init
Ми можемо створювати екземпляр одинака всередині функції init
. Це можливо тільки у тих випадках, коли рання ініціалізація екземпляра не є проблемою. Функція init
викликається лише один раз для кожного файлу в пакеті, тому ми можемо бути впевнені в тому, що буде створений екземпляр буде єдиним.
-
sync.Once
Sync.Once
виконає операцію лише один раз. Дивіться код нижче:
syncOnce.go: Одинак
package main
import (
"fmt"
"sync"
)
var once sync.Once
type single struct {
}
var singleInstance *single
func getInstance() *single {
if singleInstance == nil {
once.Do(
func() {
fmt.Println("Creating single instance now.")
singleInstance = &single{}
})
} else {
fmt.Println("Single instance already created.")
}
return singleInstance
}
main.go: Клієнтський код
package main
import (
"fmt"
)
func main() {
for i := 0; i < 30; i++ {
go getInstance()
}
// Scanln is similar to Scan, but stops scanning at a newline and
// after the final item there must be a newline or EOF.
fmt.Scanln()
}
output.txt: Результат виконання
Creating single instance now.
Single instance already created.
Single instance already created.