Внедрение зависимостей

Автор:Игорь Тельменко

Внедрение зависимостей

Внедрение зависимостей (Dependency injection, или DI) — это принцип настройки объекта, при котором поля объекта задаются внешним объектом (сущностью) в противопоставление самонастройке объектов.

Разрешение зависимостей внутри класса

Рассмотрим пример:

Здесь класс сам разрешает собсвенные зависимости. Если нужно будет поменять зависимости (подключиться к другому источнику данных), то придется менять код.

Внедряем зависимости через конструктор

Теперь немного изменим код:

Класс уже не разрешает зависимости. Эту задачу теперь выполняет вызывающий код, который передаст нужные параметры. Зависимости внедряются через конструктор. Это и есть внедрение зависимостей в простейшем виде. Теперь уже можно сменить источник без изменения класса PersonData. Внедрять зависимости конечно можно не только через конструктор, но и через методы-сеттеры, например.

Извабвляемся от лишних зависимостей

Но класс PersonData все еще зависит от класса DataSourceImplemenation и от интерфейса DataSource. Хотя ему нужно знать только о последнем. Вместо внедрения четырех параметров мы можем внедрить только лишь DataSource.

Теперь мы может передавать в PersonData любую реализацию DataSource и наш класс не зависит от DataSourceImplemenation и 4 строковых параметров.

Цепное внедрение зависимостей

Допустим наш класс PersonData используется классом PersonReport

Теперь любому коду, который использует PersonData приходится разрешать зависиомости для него. В итоге PersonReport зависит теперь и от PersonData и от данных, которые не нужны конкретно ему, а нужны следующему слою абстракции.

Чтобы справиться с этими проблемами необходимо продолжить внедрение зависимостей по всем слоям абстракции.

Такой паттерн внедрения должен идти через все слои.

Контейнер Dependency Injection Container

В реальной системе есть множество компонентов, которые имеют свои зависимости. И нам требуется сущность, которая бы знала какие зависимости внедрять в каждый класс. Она будет представлять собой нечто вроде фабрики, но с возможностью дальнейшего управления жизненным циклом объектов, которых он создал. Например, для освобождения ресурсов после использования. Такую сущность называют контейнером (Dependency Injection Container или DIC)

Существуют различные реализации таких контейнеров. Например, в Laravel имеется свой контейнер.

Выгоды от использования DI и DIC

Благодаря использованию DI и DIC мы получаем следующие выгоды

  • Меньше зависимостей у отдельных компонентов. А чем меньше зависимосей, тем меньше возникает необходимость изменений компонента
  • Меньше перенос зависимостей, и в связи с этим меньше кода
  • Код удобнее читать, так как зависимости перенесены в интерфес компонента. Нет нужды просматировать весь код компонента
  • Проще тестировать код, так как легко можно подменять реализацию на mock-объекты и компоненты меньше связаны
  • Проще переиспользовать компонент в другом контексте

Как работает контейнер в Laravel

В Laravel функцию контейнера Dependency Injection выполняет Service Container. Соответствующий код можно посмотреть в файле vendor/laravel/framework/src/Illuminate/Container/Container.php в методе build().

Для определения зависимостей используются подсказки типов (type hints) в коде. Хорошо описывается работа данного кода в статье Dependency Injection in Laravel на Medium. Если кратко, то, используя Reflection API в PHP, контейнер изучает какие параметры каких типов (классов) передаются в конструктор и так определяет какие зависимости требуются для создания экземпляра конкретного класса.

Полезные ссылки

Оставить ответ