Использовали ли вы паттерн Singleton и в каких случаях?

«Использовали ли вы паттерн Singleton и в каких случаях?» — вопрос из категории Паттерны, который задают на 10% собеседований Java Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Да, использовал. Singleton — это порождающий паттерн, гарантирующий существование только одного экземпляра класса и предоставляющий глобальную точку доступа к нему.

Типичные сценарии использования:

  • Конфигурация приложения (глобальный объект настроек).
  • Логгеры.
  • Кэши.
  • Подключения к базе данных или пулы соединений (с осторожностью).

Пример реализации на Java (ленивая инициализация):

public class Singleton {
    private static volatile Singleton instance;
    private Singleton() {} // Приватный конструктор

    public static Singleton getInstance() {
        if (instance == null) { // Первая проверка (без блокировки)
            synchronized (Singleton.class) {
                if (instance == null) { // Вторая проверка (под блокировкой)
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

Ключевые аспекты и нюансы:

  1. Потокобезопасность: Базовая реализация не является потокобезопасной. Для этого используется Double-Checked Locking (как в примере выше) с полем volatile или синхронизация всего метода getInstance() (что менее эффективно).
  2. Способы инициализации:
    • Ленивая (Lazy): Экземпляр создается при первом вызове getInstance().
    • Жадная (Eager): private static final Singleton INSTANCE = new Singleton(); — экземпляр создается при загрузке класса.
  3. Проблемы и нарушения: Паттерн может быть нарушен через:
    • Рефлексию (можно вызвать приватный конструктор).
    • Сериализацию/десериализацию (создастся новый объект).
    • Клонирование.
  4. Рекомендуемая альтернатива (в Java): Использование enum, который по своей природе гарантирует единственный экземпляр и защищен от рефлексии и сериализации.
    public enum EnumSingleton {
        INSTANCE;
        public void doSomething() { /* ... */ }
    }
  5. Современные альтернативы: Внедрение зависимостей (DI) через фреймворки (Spring, Guice), где контейнер сам управляет жизненным циклом и единственностью бинов (@Singleton / @Scope("singleton")).