Что такое Внедрение зависимостей? Dependency injection.
Содержание
Очень часто на собеседовании встречается вопрос о том что такое Внедрение зависимостей, или Dependency injection на английском. Давайте вместе с вами разберемся что же это такое, когда и зачем применять внедрение зависимостей и какие от него плюсы. Естественно с использованием примеров.
Сразу хочу обратить внимание, что в принципах SOLID последний принцип называется Dependency inversion. Так вот вам первый инсайт. Это совершенно разные вещи. Абсолютно разные и они ни коем образом не связаны между собой.
Давайте рассмотрим для начала два флоу выполнения кода приложения: используя внедрение зависимостей и без него.
Схема работы внедрения зависимостей
Без использования Dependency Injection:
- Приложению нужно Foo (к примеру, контроллер)
- Приложение создает Foo
- Приложение вызывает Foo
- Foo нужен Bar (это может быть какой-то сервис)
- Foo создает Bar
- Foo вызывает Bar
- Bar нужен Bim (какой-то репозиторий)
- Bar создает Bim
- Bar делает что-то
- …
Теперь с использованием внедрения зависимостей:
- Приложению нужен Foo которому нужен Bar которому, в свою очередь, нужен Bim
- Приложение создает Bim
- Приложение создает Bar и передает в него уже созданный Bim
- Приложение создает Foo, в который передает выше созданный Bar
- Приложение вызывает Foo
- Foo вызывает Bar
- Bar делает что-то
- Foo вызывает Bar
На глаз видна немного разница. Данный паттерн называется принципом Инверсия управления (Inversion of Control). Управление зависимостями происходит в обратном порядке: из тех, которые будут вызваны теми кто вызывают. В данном выше примере от Bim к Foo.
Преимущества использования внедрения зависимостей.
Главное преимущество использования внедрения зависимостей – вы всегда находитесь на верху вызываемой цепочки. Вы контролируете все зависимости и имеете контроль над работой вашего приложения. Вы можете заменять одни зависимости другими.
К примеру, какая-то библиотека LibA использует DB1, а вам нужно использовать DB2 в своем приложении. Используя Dependency Injection у вас пропадет надобность изменения какого-то кода в LibA.
Пример Dependency Injection
Рассмотрим реальный пример реализации без использования и с использованием Dependency Injection.
Без использования Dependency Injection
Предположим, у нас есть:
class GoogleMaps { public function getCoordinatesFromAddress(string $address) { // вызывает сервис Google Maps } } class OpenStreetMap { public function getCoordinatesFromAddress(string $address) { // вызывает сервис OpenStreetMap } }
Стандартный способ использования:
class StoreService { public function getStoreCoordinates($store) { $geolocationService = new GoogleMaps(); return $geolocationService->getCoordinatesFromAddress($store->getAddress()); } }
Как вы можете заметить, все будет замечательно работать. Но. Теперь мы хотим использовать OpenStreetMap вместо GoogleMaps по своим соображениям. Что нам нужно для этого сделать? Изменить код в нашем StoreService и так же в других классах, которые используют GoogleMaps. А таких классов может быть довольно много.
Без применения внедрения зависимостей наш класс очень тесно связан с его зависимостями. И это очень плохо.
С использованием Dependency Injection
Сделаем теперь так что бы наш класс StoreService использовал Dependency Injection.
class StoreService { private $geolocationService; public function __construct(GeolocationService $geolocationService) { $this->geolocationService = $geolocationService; } public function getStoreCoordinates($store) { return $this->geolocationService->getCoordinatesFromAddress($store->getAddress()); } }
Далее добавим интерфейс реализуем его в наших сервисах.
interface GeolocationService { public function getCoordinatesFromAddress($address); } class GoogleMaps implements GeolocationService { ... } class OpenStreetMap implements GeolocationService { ... }
Теперь пользователь может сам решать какой из сервисов использовать. И это можно поменять в любой момент без редактирования StoreService.
Таким образом наш StoreService больше не связан тесно с его зависимостями.
Недостатки внедрения зависимостей
Несмотря на то что все так замечательно и независимо стало у нас работать, у внедрения зависимостей есть один существенный недостаток – его нужно реализовать в вашем проекте.
Для этого нам поможет PHP-DI. В различных фреймворках это уже реализовано и вам достаточно просмотреть документацию что бы разобраться как это использовать.
Вместо использования
$geolocationService = new GoogleMaps(); $storeService = new StoreService($geolocationService);
Мы сможем написать
$storeService = $container->get('StoreService');
И с помощью PHP-DI установить зависимости.
$container->set('GeolocationService', \DI\create('GoogleMaps'));
Если в дальнейшем вы решите использовать другую зависимость – вам достаточно будет поменять всего одну последнюю строку.