Как правильно реализовать принцип внедрения зависимостей из SOLID?
Что проверяют
Различаете ли вы принцип и инструмент. Буква D в SOLID это инверсия зависимостей, а контейнер внедрения зависимостей лишь один из способов её реализовать.
Как отвечать
Начните с самого принципа: модули верхнего уровня не должны зависеть от модулей нижнего уровня, оба зависят от абстракций. Практически это значит, что сервис зависит от интерфейса хранилища, а не от конкретного класса с обращением к базе, и интерфейс принадлежит слою бизнес-логики, а не слою данных.
Как это выглядит в коде:
public class OrderService(IOrderRepository repository, IClock clock)
{
// зависимости приходят снаружи, класс не создаёт их сам
}Внедрение через конструктор предпочтительно: зависимости обязательны, поле можно сделать неизменяемым, а список параметров честно показывает, что нужно классу.
В ASP.NET Core регистрация идёт в контейнере, и здесь стоит назвать времена жизни, потому что это главный источник ошибок:
- transient создаётся при каждом запросе зависимости;
- scoped живёт в пределах одного HTTP-запроса, так регистрируется контекст базы;
- singleton один на приложение.
Классическая ошибка это захваченная зависимость: объект-одиночка получает в конструктор зависимость с областью запроса и удерживает её навсегда. Контейнер умеет ловить это при проверке области видимости.
Полезно добавить границу: не всё нужно прятать за интерфейс. Если реализация одна и подменять её незачем, интерфейс добавляет слой без пользы. Абстракция вводится ради подмены в тестах или ради реальной вариативности.
Где спотыкаются
Заводят интерфейс на каждый класс механически и получают двойное количество файлов без выигрыша.
Регистрируют контекст базы как одиночку и получают проблемы с параллельным доступом.
Используют контейнер как локатор служб, получая зависимости из статического доступа, и теряют всю пользу от явных конструкторов.