Какие недостатки паттерна Singleton и почему его иногда называют антипаттерном?

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

Ответ

Singleton часто критикуют как антипаттерн из-за проблем, которые он вносит в архитектуру приложения:

Основные недостатки:

  1. Глобальное состояние: Объект синглтона действует как глобальная переменная. Это усложняет модульное тестирование, так как состояние может сохраняться между тестами, делая их зависимыми друг от друга.
  2. Скрытые зависимости: Классы, использующие синглтон, не объявляют эту зависимость явно (например, через параметры конструктора), что нарушает принцип инверсии зависимостей и ухудшает читаемость кода.
  3. Нарушение принципа единственной ответственности (SRP): Singleton часто становится «божественным объектом», который знает и делает слишком много.
  4. Проблемы с многопоточностью: Наивная реализация небезопасна для потоков. Корректная реализация требует дополнительных усилий (например, double-checked locking, holder или enum).

Пример проблемной реализации:

public class NaiveSingleton {
    private static NaiveSingleton instance;
    private NaiveSingleton() {}
    // НЕПОТОКОБЕЗОПАСНО! При одновременном вызове из двух потоков
    // могут создаться два экземпляра.
    public static NaiveSingleton getInstance() {
        if (instance == null) {
            instance = new NaiveSingleton();
        }
        return instance;
    }
}

Альтернатива: Вместо явного синглтона предпочтительнее использовать внедрение зависимостей (DI). Контейнер (Spring, Guice) управляет жизненным циклом объекта, создавая его в единственном экземпляре (scope singleton) и явно внедряя туда, где это необходимо. Это сохраняет преимущество единого экземпляра, но устраняет недостатки антипаттерна.