Введение
Техника программирования программного обеспечения
В разработке программного обеспечения, внедрение зависимостей — это техника программирования, в которой объект или функция получает другие объекты или функции, необходимые ему для работы, вместо того чтобы создавать их самостоятельно. Внедрение зависимостей направлено на разделение задач создания объектов и их использования, что приводит к слабосвязанным программам. Этот подход гарантирует, что объекту или функции, желающим использовать определенный сервис, не нужно знать, как этот сервис создать. Вместо этого, принимающий "клиент" (объект или функция) получает свои зависимости от внешнего кода ("инъектора"), о котором он не осведомлен. Внедрение зависимостей делает неявные зависимости явными и помогает решить следующие проблемы: как сделать класс независимым от создания объектов, от которых он зависит? Как обеспечить поддержку различных конфигураций приложением и используемыми им объектами? Внедрение зависимостей часто используется для обеспечения соответствия кода принципу инверсии зависимостей. В статически типизированных языках использование внедрения зависимостей означает, что клиенту необходимо только объявлять интерфейсы используемых сервисов, а не их конкретные реализации, что упрощает изменение используемых сервисов во время выполнения без перекомпиляции. Приложения часто сочетают внедрение зависимостей с инверсией управления. При инверсии управления, фреймворк сначала создает объект (например, контроллер), а затем передает ему управление. При внедрении зависимостей фреймворк также создает экземпляры зависимостей, объявленных объектом приложения (часто в параметрах метода конструктора), и передает эти зависимости в объект. Внедрение зависимостей реализует идею "инвертирования контроля над реализациями зависимостей", поэтому некоторые Java-фреймворки обобщенно называют эту концепцию "инверсией управления" (не следует путать с инверсией потока управления).
How can a class be independent from the creation of the objects it depends on? How can an application, and the objects it uses support different configurations? Dependency injection is often used to keep code in line with the dependency inversion principle. In statically typed languages using dependency injection means a client only needs to declare the interfaces of the services it uses, rather than their concrete implementations, making it easier to change which services are used at runtime without recompiling. Application frameworks often combine dependency injection with inversion of control. Under inversion of control, the framework first constructs an object (such as a controller), and then passes control flow to it. With dependency injection, the framework also instantiates the dependencies declared by the application object (often in the constructor method's parameters), and passes the dependencies into the object. Dependency injection implements the idea of "inverting control over the implementations of dependencies," which is why certain Java frameworks generically name the concept "inversion of control" (not to be confused with inversion of control flow).
Роли
Инъекция зависимостей включает в себя четыре роли: сервисы, клиенты, интерфейсы и внедряющие средства.
Услуги и клиенты
Служба — это любой класс, содержащий полезную функциональность. Клиент — это любой класс, использующий службы. Службы, необходимые клиенту, являются его зависимостями. Любой объект может выступать в роли службы или клиента; эти названия относятся лишь к роли, которую объект играет в процессе внедрения зависимостей. Один и тот же объект может быть одновременно клиентом (использующим внедренные службы) и службой (внедряемой в другие объекты). После внедрения зависимостей служба становится частью состояния клиента и доступна для использования.
Интерфейсы
Клиенты не должны знать, как реализованы их зависимости, только их имена и API. Например, сервис, извлекающий электронные письма, может использовать протоколы IMAP или POP3 внутри себя, но эта деталь, скорее всего, не важна для кода, которому просто нужно получить электронное письмо. Игнорируя детали реализации, клиенты не нуждаются в изменениях при изменении их зависимостей.
Инжекторы
Инжектор, иногда также называемый сборщиком, контейнером, поставщиком или фабрикой, внедряет сервисы клиенту. Роль инжекторов заключается в создании и соединении сложных графов объектов, где объекты могут выступать как в роли клиентов, так и в роли сервисов. Сам инжектор может состоять из множества взаимодействующих объектов, но не должен быть клиентом, поскольку это привело бы к циклической зависимости. Поскольку внедрение зависимостей отделяет процесс создания объектов от процесса их использования, это часто снижает значимость ключевого слова `new`, используемого в большинстве объектно-ориентированных языков. Поскольку создание сервисов берет на себя фреймворк, программист обычно непосредственно создает только объекты-значения, представляющие сущности предметной области программы (например, объект `Employee` в бизнес-приложении или объект `Order` в приложении для онлайн-покупок).
Аналогия
В качестве аналогии, автомобили можно рассматривать как услуги, выполняющие полезную работу по перевозке людей из одного места в другое. Автомобильные двигатели могут работать на газе, дизельном топливе или электричестве, но эта деталь несущественна для клиента – водителя, которому важно лишь то, доставит ли автомобиль его до места назначения. Автомобили предоставляют унифицированный интерфейс посредством педалей, рулевого колеса и других органов управления. Следовательно, тип двигателя, установленного на заводе, перестает иметь значение, и водители могут пересаживаться в автомобили разных типов по мере необходимости.
Преимущества
Основным преимуществом внедрения зависимостей является снижение связанности между классами и их зависимостями. Устраняя у клиента знание о реализации его зависимостей, программы становятся более повторно используемыми, тестируемыми и поддерживаемыми. Это также повышает гибкость: клиент может работать с любым компонентом, реализующим интерфейс, который он ожидает. В более широком смысле, внедрение зависимостей уменьшает объем шаблонного кода, поскольку создание всех зависимостей осуществляется единым компонентом.
Испытания
Многие преимущества внедрения зависимостей особенно актуальны для модульного тестирования. Например, внедрение зависимостей можно использовать для вынесения деталей конфигурации системы во внешние файлы конфигурации, что позволяет переконфигурировать систему без перекомпиляции. Можно создавать отдельные конфигурации для различных ситуаций, требующих различных реализаций компонентов. Кроме того, поскольку внедрение зависимостей не требует изменений в поведении кода, его можно применять к устаревшему коду в процессе рефакторинга. Это делает компоненты более независимыми и упрощает их модульное тестирование в изоляции с использованием заглушек или имитаторов, которые эмулируют поведение других объектов, не подлежащих тестированию. Эта простота тестирования часто является первым преимуществом, которое замечают при использовании внедрения зависимостей.