Как работает принцип внедрения зависимостей из SOLID?
Что проверяют
Различаете ли вы принцип инверсии зависимостей (буква D в SOLID) и технику внедрения зависимостей, которой его обычно реализуют. Следом почти всегда просят показать это на коде и спрашивают, нужен ли в Python отдельный DI-контейнер.
Как отвечать
Начните с поправки к вопросу: буква D в SOLID означает Dependency Inversion Principle. Модули верхнего уровня не зависят от модулей нижнего, оба зависят от абстракций. Dependency Injection это способ добиться такой инверсии: объект не создаёт зависимости сам, а получает их снаружи.
Каркас ответа на Python:
- абстракция:
typing.Protocol(структурная типизация, наследование не нужно) илиabc.ABCс@abstractmethod. Сервис объявляет, что ему нуженUserRepository, а не конкретныйPostgresUserRepository; - внедрение через конструктор: основной вариант,
def __init__(self, repo: UserRepository). Зависимости видны в сигнатуре, и объект нельзя создать недоукомплектованным; - корень композиции: одно место, где собираются конкретные реализации, например
main.pyили фабрика приложения; - инструменты: в FastAPI встроенный
Depends, а в тестах зависимость подменяется черезapp.dependency_overrides. Для остального кода есть библиотеки вродеdependency-injector, но в небольшом проекте хватает ручной сборки.
Главный практический выигрыш это тесты: вместо базы передаётся фейк или Mock, и не нужно патчить модули через unittest.mock.patch по строковому пути.
Где спотыкаются
Называют DI буквой D из SOLID и на этом останавливаются. Интервьюер ждёт, что вы разведёте принцип и технику и объясните, куда направлена зависимость после инверсии.
Внедряют зависимость, но типизируют её конкретным классом. Формально объект пришёл снаружи, а связанность осталась: подменить реализацию, не наследуясь от неё, уже не выйдет.
Тащат тяжёлый контейнер туда, где хватило бы аргумента конструктора, или прячут зависимость в глобальный синглтон модуля и потом не могут изолировать тесты друг от друга.