Что такое Внедрение зависимостей? 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 делает что-то

На глаз видна немного разница. Данный паттерн называется принципом Инверсия управления (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'));

Если в дальнейшем вы решите использовать другую зависимость – вам достаточно будет поменять всего одну последнюю строку.

Leave a Reply