Почему в Dependency Injection обычно передают интерфейсы, а не конкретные реализации
Что проверяют
Понимаете ли вы, зачем нужна абстракция, или заучили правило «на каждый класс интерфейс». Хороший ответ обязательно содержит границу применимости, а не только список достоинств.
Как отвечать
Главное: интерфейс разворачивает направление зависимости. Потребитель объявляет, что ему нужно, конкретный класс это удовлетворяет, и ссылка между сборками идёт от инфраструктуры к домену, а не наоборот. Без этого выделение слоёв не держится.
Дальше практические следствия:
- подмена в тестах: библиотеки вроде
MoqиNSubstituteподменяют интерфейс целиком, а у класса в C# методы не виртуальны по умолчанию, поэтому безvirtualего не подменить; - декораторы: кэш, повтор при ошибке, логирование добавляются обёрткой над тем же контрактом, код потребителя не меняется;
- разные реализации по окружению: заглушка для локального запуска, настоящий клиент на бою, и меняется только строка регистрации в
IServiceCollection.
Обязательно добавить оговорку, иначе ответ звучит заученным. Интерфейс с единственной реализацией, повторяющий публичные методы класса один в один, абстракцией не является: это второй файл, который правится вместе с первым. Контракт формулируется от потребителя и должен быть уже класса. Регистрировать конкретный тип нормально, если он не пересекает границу слоя, а для настроек есть IOptions<T> с обычным классом.
Где спотыкаются
Путают внедрение зависимостей и контейнер. Внедрение это передача зависимости в конструктор, контейнер лишь способ её собрать. Если в конструктор приходит IServiceProvider и сервисы достаются из него, это локатор служб, и все преимущества теряются.
Второе: заводят интерфейс, протекающий деталями хранилища. Метод, возвращающий IQueryable, или контракт с SaveChangesAsync намертво привязывают потребителя к ORM, и подменить реализацию уже не выйдет.
Третье: ошибаются со временем жизни. Одиночка, захвативший зависимость на область запроса, живёт с одним и тем же DbContext на всё приложение, и всплывает это не сразу, а под нагрузкой, чужими данными и падениями при параллельном доступе.