Почему Singleton считается антипаттерном и какие у него проблемы
Что проверяют
Понимаете ли вы, что претензии предъявляются не к «одному экземпляру», а к способу его получения через статику. На C# ждут заодно знания потокобезопасных реализаций и умения объяснить, чем AddSingleton в контейнере отличается от классического одиночки.
Как отвечать
Разложите проблемы по пунктам:
- скрытая зависимость: обращение к
Config.Instanceне видно в сигнатуре конструктора, по коду класса нельзя понять, от чего он зависит, а в тесте нечего подменить; - глобальное изменяемое состояние: объект меняет кто угодно, порядок изменений неочевиден, тесты начинают зависеть друг от друга и падать при параллельном прогоне;
- время жизни: объект живёт до конца процесса, детерминированно освободить ресурсы через
IDisposableуже нельзя; - потокобезопасность: наивная проверка на
nullбез блокировки даёт два экземпляра, а двойная проверка требуетvolatile. НадёжнееLazy<T>или статическое поле, инициализацию которого гарантирует среда.
Сильный ответ заканчивается альтернативой: единственность экземпляра сама по себе нормальна, плохо то, что класс обеспечивает её сам. Регистрация через AddSingleton оставляет класс обычным, зависимости остаются в конструкторе, подмена в тесте становится рутиной.
Где спотыкаются
Считают AddSingleton тем же антипаттерном под другим именем. Разница принципиальная: экземпляр один, но владеет им контейнер, а класс остаётся тестируемым и не знает о своей единственности.
Второе: делают синглтоном то, что не потокобезопасно и не рассчитано на долгую жизнь. Классика это DbContext: он задуман на время запроса, накапливает отслеживаемые сущности и ломается при одновременном доступе из разных потоков.
Третье: захватывают в синглтон зависимость с коротким временем жизни. Scoped-сервис, попавший в поле долгоживущего объекта, переживает свой запрос и утаскивает чужие данные в следующий. Проверка областей в режиме разработки такие связки ловит, отключать её не стоит.