Почему Singleton нарушает принцип единой ответственности
Что проверяют
Умеете ли вы формулировать SRP точно, а не как «класс должен делать одно дело». И отличаете ли классического одиночку из книги банды четырёх от бина со скоупом singleton в Spring, потому что на Java-собеседованиях эти вещи подменяют постоянно.
Как отвечать
SRP говорит про причину изменения: у класса должна быть ровно одна причина меняться. Одиночка берёт на себя две. Первая это его предметная работа, ради которой он написан. Вторая это управление собственным жизненным циклом: приватный конструктор, статическое поле, решение о том, когда создаваться и сколько экземпляров существует.
Отсюда и проявления. Понадобилось создавать объект лениво, или по одному на арендатора, или два разных в тестах, и править приходится сам класс, хотя предметная логика не менялась. Это и есть вторая причина для изменения.
Правильный вывод: ответственность за количество экземпляров надо вынести наружу. В Spring так и сделано, бин со скоупом singleton остаётся обычным классом с конструктором и зависимостями, а единственность обеспечивает контейнер. Такой класс спокойно создаётся в тесте с подменёнными зависимостями.
Где спотыкаются
Отвечают «нарушает, потому что тяжело тестировать». Это следствие, а не определение. Тестируемость страдает из-за глобального состояния и скрытой зависимости, а SRP нарушается именно совмещением двух причин изменения, и интервьюер ждёт этой формулировки.
Второе: путают одиночку и singleton-скоуп Spring. В первом случае класс сам себя ограничивает, во втором про единственность знает только контейнер. Заявить, что Spring повсюду использует антипаттерн, значит показать, что разница не улавливается.
Третье: пишут ленивую инициализацию с проверкой на null без синхронизации либо двойную проверку без volatile. Без него другой поток увидит ссылку на ещё не достроенный объект. Надёжнее идиома с вложенным статическим классом или enum.