Что такое dependency injection






Если вы занимаетесь разработкой программного обеспечения хотя бы какое-то время, вы, скорее всего, уже натыкались на термин «dependency injection» или «внедрение зависимости». Если вы ещё только-только присоединились к миру разработки ПО, то, вероятно, пока старались избегать попыток разобраться с этой концепцией. Но что бы вам ни казалось, внедрение зависимости является отличным инструментом при разработке поддерживаемого и тестируемого кода. В сегодняшней статье автор попытается рассказать, что же такое dependency injection настолько просто, насколько он сможет.

Injection

Рассмотрим простой кусок кода:

На первый взгляд, всё просто и безобидно. Да, если не обращать внимания на тот факт, что в класс уже жёстко внедрена зависимость в виде подключения к БД. Что мы будем делать, если понадобиться хранить фотографии не в базе данных, а, скажем, на диске? Да и вообще, подумайте, с какого перепугу объект фотографии должен осуществлять изнутри себя какое-либо взаимодействие с внешним миром? Разве это не противоречит принципу разделения ответственности? Однозначно противоречит. Объект не должен  знать ни о чём другом, кроме того, что касается непосредственно самого объекта.

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

Итак, давайте переконструируем наш объект Photo так, чтобы можно было снаружи определять его поведение в отношении внешнего хранилища. Сделать это можно двумя способами. Давайте рассмотрим каждый из них.

Внедрение в конструкторе

В приведённом примере внедрение зависимости происходит в момент создания экземпляра объекта, вместо того, чтобы жёстко внедрять зависимость в классе.

Внедрение методом

В результате небольшого исправления объекты класса теперь не зависят от подключения к БД в момент создания. При этом сам класс стал удобней для юнит-тестирования. Ко всему прочему стало возможным теперь в любой момент менять подсистему хранения наших фотографий.

Проблема

Всплывает, однако, неприятный момент: с классом стало сложнее работать. Пользователи класса теперь должны помнить о том, как внедрять зависимости в объекты класса, прежде чем те смогут нормально функционировать. Представим, что наш фото-класс имеет больше зависимостей:

Упс. Вместо того, чтобы облегчить жизнь, мы добавили лишней сложности. Если раньше пользователь просто создавал объект и работал с ним, то теперь ему придётся изрядно потанцевать с бубном, прежде чем двигаться дальше.

Решение

Решение заключается в том, чтобы создать отдельный класс-контейнер, который будет делать за нас всю тяжёлую работу. Если вы когда-нибудь сталкивались с термином Inversion of Control (IoC) — инверсия управления, то вы, вероятно, в курсе, о чём речь.

Класс-контейнер хранит информацию обо всех зависимостях, присутствующих в проекте, и выполняет всю работу по созданию зависимых объектов, а также внедрению в них зависимостей. Можно разными способами реализовать этот подход, мы же пока ясности ради реализуем всё прямо в методах класса-контейнера:

Теперь всё вернулось на круги своя: одним-единственным вызовом метода класса-контейнера мы получаем полностью готовый к употреблению экземпляр объекта Photo.

Лучшим решением, всё-таки, будет более общее решение класса-контейнера:

Данный класс предоставляет возможность регистрировать резолверы (функции, выполняющие непосредственную работу по созданию объектов и внедрению зависимостей) при помощи метода register() и позднее использовать метод resolve() для того, чтобы получать экземпляры объектов классов. Например, зарегистрируем резолвер для нашего класса Photo:

И теперь там, где нам нужен экземпляр объекта, получаем его простым и понятным способом:

Теперь оба зайца убиты: зависимости вынесены за пределы класса, при этом сохранилась простота работы с ним. Немножко добавилось писанины:

Однако это совершенно ничего не стоит в сравнении с великолепной логической организацией классов, удобством использования и тестирования! В реальных приложениях, кроме того, очень полезно научить класс-контейнер создавать и возвращать Singleton-объекты.

Магические методы

Если пойти ещё дальше, желая минимизировать и упростить сам класс-контейнер, можно задействовать магические методы __set() и __get():

 

Теперь добавлять и использовать резолверы стало намного интуитивно-понятнее:

Источник




Что такое dependency injection: 10 комментариев

  1. В первой реализации вы слукавили и создали на самом деле статический класс с фабричным методом, а не контейнер. :)

    Плюс ещё неплохо было бы передавать в замыкание ссылку на сам контейнер ... хотя это уже не для учебных целей ...

    1. Благодарю за замечание! Статья переводная, а сам я, к сожалению, в шаблонах проектирования плаваю ужасно. Не посоветуете ли что лучше почитать по теме? Не обязательно применительно к PHP. Спасибо ещё раз.

      1. Ссылки на Озон, но в сети можно найти издания на русском и английском в электронном виде.

        www.ozon.ru/context/detail/id/1308678/ (советую прочитать для начала, даже перед шаблонами банды 4-х).

        www.ozon.ru/context/detail/id/4884925/

        www.ozon.ru/context/detail/id/3938906/

        www.ozon.ru/context/detail/id/5497184/ (для более подготовленных читателей)

        www.ozon.ru/context/detail/id/3105480/

        www.amazon.com/Software-D...es/dp/0135974445

        Много идей можно вытащить из устройства компонент Zend Framework-a, если вы пишите на PHP. Я застал ещё 1-ую версию, потом сменил язык. Сейчас вроде интересные идеи реализуются в Symphony2/Doctrine2.

        Кстати, уже реализованный контейнер: symfony-gu.ru/documentati...e_container.html

      1. Вторую не читал, а первая это можно сказать первоисточник по шаблонам. Правда если мало представления о C++, то будет плохо понятны приведённые примеры. Хотя в большей степени кросс-языковые описаны. Хотя есть такие, которые вы в других языках и почти не встретите или очень редко. Например, шаблон Мост (Bridge).

  2. Хорошая статья, очень подробно расписано про шаблон. Я долго не мог въехать в сути внедрения зависимостей, пока не попробовал Symfony2 в серьезном крупном проекте. Сейчас просто влюблен в этот паттерн, даже свою небольшую но удобную реализацию использую часто jakulov.ru/blog/2014/bun_dependency_injection

Комментарии запрещены.