Одинак — це породжуючий патерн, який гарантує існування тільки одного об’єкта певного класу, а також дозволяє дістатися цього об’єкта з будь-якого місця програми.
Одинак має такі ж переваги та недоліки, що і глобальні змінні. Його неймовірно зручно використовувати, але він порушує модульність вашого коду.
Ви не зможете просто взяти і використовувати клас, залежний від одинака, в іншій програмі. Для цього доведеться емулювати там присутність одинака. Найчастіше ця проблема проявляється при написанні юніт-тестів.
Зазвичай, екземпляр одинака створюється під час початкової ініціалізації структури. Для цього, ми визначаємо для структури метод getInstance. Цей метод буде створювати і повертати екземпляри одинака. Після створення першого екземпляра, під час кожного виклику методу getInstance буде повертатися саме він.
А що стосовно потоків goroutine? Структура одинака повинна повертати один і той же екземпляр у випадках, коли різні потоки намагаються отримати доступ до цього екземпляра. З цієї причини можна легко помилитися і неправильно реалізувати патерн Одинак. Приклад нижче показує, як правильно створити Одинака.
Деякі важливі деталі, про які потрібно пам’ятати:
На початку потрібна nil-перевірка, за допомогою неї ми переконуємося, що перший примірник singleInstance — порожній. Завдяки цьому ми можемо уникнути ресурсномістких операцій блокування під час кожного виклику getInstance. Якщо ця перевірка не пройдена, тоді поле singleInstance вже заповнено.
Структура singleInstance створюється всередині блокування.
Після блокування використовується ще одна nil-перевірка. У випадках, коли першу перевірку проходить понад одного потоку, друга забезпечує створення екземпляра одинака лише одним потоком. Інакше всі потоки створювали б свої екземпляри структури одинака.
single.go: Одинак
main.go: Клієнтський код
output.txt: Результат виконання
Інший приклад
Є і інші методи створення екземпляра одинака в Go:
Функція init
Ми можемо створювати екземпляр одинака всередині функції init. Це можливо тільки у тих випадках, коли рання ініціалізація екземпляра не є проблемою. Функція init викликається лише один раз для кожного файлу в пакеті, тому ми можемо бути впевнені в тому, що буде створений екземпляр буде єдиним.
sync.Once
Sync.Once виконає операцію лише один раз. Дивіться код нижче: