Одиночка — это порождающий паттерн, который гарантирует существование только одного объекта определённого класса, а также позволяет достучаться до этого объекта из любого места программы.
Одиночка имеет такие же преимущества и недостатки, что и глобальные переменные. Его невероятно удобно использовать, но он нарушает модульность вашего кода.
Вы не сможете просто взять и использовать класс, зависящий от одиночки в другой программе. Для этого придётся эмулировать присутствие одиночки и там. Чаще всего эта проблема проявляется при написании юнит-тестов.
Обычно, экземпляр одиночки создается во время начальной инициализации структуры. Для этого, мы определяем для структуры метод getInstance. Этот метод будет создавать и возвращать экземпляры одиночки. После создания первого экземпляра, при каждом вызове метода getInstance будет возвращаться именно он.
А что касательно потоков goroutine? Структура одиночки должна возвращать один и тот же экземпляр в случаях, когда разные потоки пытаются получить доступ к этому экземпляру. По этой причине можно легко ошибиться и неправильно реализовать паттерн Одиночка. Пример ниже показывает, как правильно создать Одиночку.
Некоторые важные детали, о которых нужно помнить:
В начале нужна nil-проверка, с ее помощью мы убеждаемся, что первый экземпляр singleInstance — пустой. Благодаря этому мы можем избежать ресурсоемких операций блокировки при каждом вызове getInstance. Если эта проверка не пройдена, тогда поле singleInstance уже заполнено.
Структура singleInstanceсоздается внутри блокировки.
После блокировки используется еще одна nil-проверка. В случаях, когда первую проверку проходит более одного потока, вторая обеспечивает создание экземпляра одиночки единым потоком. В противном случае, все потоки создавали бы свои экземпляры структуры одиночки.
single.go: Одиночка
main.go: Клиентский код
output.txt: Результат выполнения
Другой пример
Существуют и другие методы создания экземпляра одиночки в Go:
Функция init
Мы можем создавать экземпляр одиночки внутри функции init. Это возможно только в тех случаях, когда ранняя инициализация экземпляра не является проблемой. Функция init вызывается единожды для каждого файла в пакете, поэтому мы можем быть уверенны в том, что будет создан только один экземпляр.
sync.Once
sync.Once выполнит операцию лишь один раз. Смотрите код ниже: