Внедрение зависимости - Dependency injection

В программная инженерия, внедрение зависимости это техника, в которой объект получает другие объекты, от которых зависит. Эти другие объекты называются зависимостями. В типичных отношениях "использования" принимающий объект называется клиент а переданный (то есть "внедренный") объект называется служба. Код, передающий службу клиенту, может быть различным.[необходимо определение ] и называется инжектором. Вместо того, чтобы клиент указывал, какую службу он будет использовать, инжектор сообщает клиенту, какую службу использовать. «Внедрение» относится к передаче зависимости (службы) объекту (клиенту), который будет ее использовать.

Услуга сделана частью клиентской государственный.[1] Передача службы клиенту вместо того, чтобы позволить клиенту создавать или найти услугу, является фундаментальным требованием шаблона.

Целью внедрения зависимостей является достижение разделение проблем строительства и использования объектов. Это может повысить удобочитаемость и повторное использование кода.

Внедрение зависимостей - одна из форм более широкой техники инверсия контроля. Клиент, который хочет вызвать некоторые службы, не должен знать, как построить эти службы. Вместо этого клиент делегирует ответственность за предоставление своих услуг внешнему коду (инжектору). Клиент не имеет права вызывать код инжектора;[2] это инжектор, который создает услуги. Затем инжектор внедряет (передает) услуги клиенту, которые могут уже существовать или могут быть созданы инжектором. Затем клиент пользуется услугами. Это означает, что клиенту не нужно знать о инжекторе, о том, как создавать сервисы, или даже о том, какие фактические сервисы он использует. Клиенту нужно знать только о внутренних интерфейсах сервисов, потому что они определяют, как клиент может использовать сервисы. Это отделяет ответственность за «использование» от ответственности за «строительство».

Намерение

Внедрение зависимостей решает такие проблемы, как:[3]

  • Как приложение или учебный класс быть независимым от того, как создаются его объекты?
  • Как можно указать способ создания объектов в отдельных файлах конфигурации?
  • Как приложение может поддерживать разные конфигурации?

Создание объектов непосредственно внутри класса негибко, потому что оно фиксирует класс для определенных объектов и делает невозможным изменение экземпляра позже независимо от (без необходимости изменения) класса. Это предотвращает повторное использование класса, если требуются другие объекты, и затрудняет тестирование класса, поскольку реальные объекты не могут быть заменены ложными объектами.

Класс больше не отвечает за создание требуемых объектов, и ему не нужно делегировать создание экземпляра объекту фабрики, как в Абстрактная фабрика[4] шаблон дизайна.
См. Также схему классов и последовательности UML ниже.

Обзор

Инъекция зависимости для пятилетних детей

Когда вы идете и достаете вещи из холодильника для себя, вы можете создать проблемы. Вы можете оставить дверь открытой, вы можете получить что-то, чего мама или папа не хотят, чтобы вы имели. Возможно, вы даже ищете то, чего у нас нет или срок действия которого истек.

Вам следует заявить о своей потребности: «Мне нужно что-нибудь выпить за обедом», а затем мы позаботимся о том, чтобы у вас было что-нибудь, когда вы сядете поесть.

Джон Манч, 28 октября 2009 г.[5][6][7]

Внедрение зависимостей отделяет создание зависимостей клиента от поведения клиента, что позволяет проектировать программы. свободно соединенный[8] и следовать инверсия зависимости и принципы единой ответственности.[5][9] Это прямо контрастирует с шаблон локатора услуг, который позволяет клиентам узнать о системе, которую они используют для поиска зависимостей.

Инъекция, основная единица внедрения зависимостей, не является новым или настраиваемым механизмом. Это работает так же, как "передача параметров " работает.[10] Ссылаясь на «передачу параметров» как на инъекцию, мы подразумеваем, что это делается для того, чтобы изолировать клиента от деталей.

Инъекция также касается того, что контролирует передачу (но не клиента), и не зависит от того, как выполняется передача, будь то передача ссылки или значения.

Внедрение зависимости включает четыре роли:

  • то служба объект (ы) для использования
  • то клиент объект, зависящий от услуг, которые он использует
  • то интерфейсы которые определяют, как клиент может использовать услуги
  • то инжектор, который отвечает за создание сервисов и внедрение их в клиент

По аналогии

  • сервис - электромобиль, газ, гибрид или дизель
  • клиент - водитель, который использует машину одинаково независимо от двигателя
  • интерфейс - автоматический, гарантирует, что водитель не должен разбираться в деталях переключения передач
  • инжектор - родитель, который купил малышу машину и решил какой

Любой объект, который может быть использован, можно считать служба. Любой объект, использующий другие объекты, можно считать клиент. Имена не имеют ничего общего с тем, для чего предназначены объекты, а все имеют отношение к роли, которую объекты играют в любой одной инъекции.

В интерфейсы - это типы, которых клиент ожидает от своих зависимостей. Проблема в том, что они делают доступным. Они действительно могут быть типами интерфейсов, реализованными службами, но также могут быть абстрактными классами или даже конкретный сами службы, хотя последнее нарушит ОКУНАТЬ[11] и пожертвовать динамической развязкой, которая позволяет проводить тестирование. Требуется только, чтобы клиент не знал, что они собой представляют, и поэтому никогда не относился к ним как к конкретным, например, путем их конструирования или расширения.

У клиента не должно быть конкретных знаний о конкретной реализации своих зависимостей. Он должен знать только имя интерфейса и API. В результате клиенту не нужно будет менять, даже если изменится то, что стоит за интерфейсом. Однако если интерфейс рефакторинг клиент должен быть перекомпилирован из класса в тип интерфейса (или наоборот).[12] Это важно, если клиент и сервисы публикуются отдельно. Это неудачное соединение не может разрешить внедрение зависимостей.

В инжектор знакомит с услугами клиента. Часто он также создает клиента. Инжектор может соединить вместе очень сложный граф объектов, рассматривая объект как клиент, а затем как услугу для другого клиента. На самом деле инжектор может представлять собой множество объектов, работающих вместе, но не может быть клиентом. Инжектор может называться другими именами, такими как: ассемблер, поставщик, контейнер, фабрика, строитель, пружина, код конструкции или основной.

Внедрение зависимостей может применяться как дисциплина, требующая, чтобы все объекты разделяли конструкцию и поведение. Использование структуры DI для выполнения конструкции может привести к запрету использования новый ключевое слово, или, менее строго, разрешить только прямое построение объекты ценности.[13][14][15][16]

Таксономия

Инверсия контроля (IoC) является более общим, чем внедрение зависимостей. Проще говоря, IoC означает позволить другому коду позвонить вам, а не настаивать на выполнении вызова. Примером IoC без внедрения зависимостей является шаблон метода шаблон. Здесь, полиморфизм достигается за счет подклассы, то есть, наследование.[17]

Внедрение зависимостей реализует IoC через сочинение так часто идентичен таковому из шаблон стратегии, но в то время как шаблон стратегии предназначен для взаимозаменяемости зависимостей по всему объекту продолжительность жизни, при внедрении зависимости может быть, что используется только один экземпляр зависимости.[18] Это все еще достигает полиморфизма, но через делегация и сочинение.

Фреймворки внедрения зависимостей

Фреймворки приложений Такие как CDI и его реализация Сварка, Весна, Guice, Фреймворк игры, Сальта, Стеклянная рыба HK2, Кинжал, и Фреймворк управляемой расширяемости (MEF) поддерживают внедрение зависимостей, но не требуются для внедрения зависимостей.[19][20]

Преимущества

  • Внедрение зависимостей позволяет клиенту гибко настраиваться. Фиксируется только поведение клиента. Клиент может действовать на все, что поддерживает внутренний интерфейс, которого ожидает клиент.[21]
  • Внедрение зависимостей может использоваться для внесения деталей конфигурации системы в файлы конфигурации, что позволяет реконфигурировать систему без перекомпиляции. Можно написать отдельные конфигурации для разных ситуаций, требующих разных реализаций компонентов. Это включает, но не ограничивается, тестирование.[22]
  • Поскольку внедрение зависимостей не требует изменения поведения кода, его можно применить к унаследованному коду как рефакторинг. В результате клиенты становятся более независимыми и которых легче модульный тест изолированно с использованием заглушки или же имитировать объекты которые имитируют другие не тестируемые объекты. Эта простота тестирования часто является первым преимуществом, заметным при использовании внедрения зависимостей.[23]
  • Внедрение зависимостей позволяет клиенту удалить все знания о конкретной реализации, которые ему необходимы. Это помогает изолировать клиента от воздействия изменений конструкции и дефектов. Это способствует повторному использованию, тестированию и ремонту.[24]
  • Сокращение шаблонный код в объектах приложения, поскольку вся работа по инициализации или настройке зависимостей выполняется компонентом поставщика.[24]
  • Внедрение зависимостей допускает одновременную или независимую разработку. Два разработчика могут самостоятельно разрабатывать классы которые используют друг друга, при этом им нужно знать только интерфейс, через который классы будут общаться. Плагины часто разрабатываются сторонними магазинами, которые даже не разговаривают с разработчиками, создавшими продукт, использующий плагины.[25]
  • Внедрение зависимостей уменьшает связь между классом и его зависимостью.[26][27]

Недостатки

  • Внедрение зависимостей создает клиентов, которым требуется предоставление деталей конфигурации с помощью строительного кода. Это может быть обременительным, когда доступны очевидные значения по умолчанию.[28]
  • Внедрение зависимостей может затруднить отслеживание (чтение) кода, поскольку оно отделяет поведение от построения. Это означает, что разработчики должны обращаться к большему количеству файлов, чтобы следить за работой системы.[29]
  • Фреймворки внедрения зависимостей реализуются с помощью отражения или динамического программирования. Это может затруднить использование таких средств автоматизации IDE, как «поиск ссылок», «отображение иерархии вызовов» и безопасный рефакторинг.[30]
  • Внедрение зависимостей обычно требует дополнительных усилий по разработке, поскольку нельзя вызвать что-то нужное, когда и где это необходимо, но нужно попросить, чтобы это было внедрено, а затем убедиться, что оно было введено.[31]
  • Внедрение зависимостей заставляет сложность перемещаться из классов в связи между классами, что не всегда может быть желательным или легко управляемым.[32]
  • Внедрение зависимостей может стимулировать зависимость от инфраструктуры внедрения зависимостей.[32][33][34]

Структура

Схема классов и последовательности UML

Пример класса UML и диаграммы последовательности для шаблона проектирования внедрения зависимостей. [35]

В приведенном выше UML диаграмма классов, то Клиент класс, который требует ServiceA и ServiceB объекты не создают экземпляр ServiceA1 и ServiceB1 классы напрямую. Инжектор класс создает объекты и внедряет их в Клиент, что делает Клиент независимо от того, как создаются объекты (какие конкретные классы создаются).
В UML схема последовательности показывает взаимодействия во время выполнения: Инжектор объект создает ServiceA1 и ServiceB1 объекты. Инжектор создает Клиент объект и вводит ServiceA1 и ServiceB1 объекты.

Примеры

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

В следующих Ява Например, класс Client содержит Service переменная-член который инициализируется Клиентом конструктор. Клиент контролирует, какая реализация службы используется, и контролирует ее построение. В этой ситуации говорят, что клиент имеет жестко заданную зависимость от ExampleService.

// Пример без внедрения зависимостейобщественный учебный класс Клиент {    // Внутренняя ссылка на службу, используемую этим клиентом    частный ExampleService служба;    // Конструктор    Клиент() {        // Укажите конкретную реализацию в конструкторе вместо использования внедрения зависимостей        служба = новый ExampleService();    }    // Метод в этом клиенте, который использует службы    общественный Нить приветствовать() {        возвращаться "Привет " + служба.getName();    }}

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

Типы внедрения зависимостей

Существует как минимум три способа, которыми клиентский объект может получить ссылку на внешний модуль:[36]

внедрение конструктора
Зависимости предоставляются через конструктор класса клиента.
сеттер инъекций
Клиент предоставляет метод установки, который инжектор использует для внедрения зависимости.
внедрение интерфейса
Интерфейс зависимости предоставляет метод инжектора, который будет внедрять зависимость в любого переданного ему клиента. Клиенты должны реализовать интерфейс, который предоставляет метод установки который принимает зависимость.

Другие типы

В структурах DI могут быть другие типы инъекция помимо представленных выше.[37]

Фреймворки тестирования также могут использовать другие типы. Некоторые современные среды тестирования даже не требуют, чтобы клиенты активно принимали внедрение зависимостей, что делает устаревший код пригодным для тестирования. В частности, в языке Java можно использовать отражение, чтобы сделать частные атрибуты общедоступными при тестировании и, таким образом, принимать инъекции по назначению.[38]

Некоторые попытки инверсии управления не обеспечивают полного удаления зависимости, а вместо этого просто заменяют одну форму зависимости другой. Как показывает практика, если программист не может смотреть только на код клиента и сказать, какая структура используется, тогда у клиента жестко запрограммирована зависимость от этой платформы.

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

Этот метод требует, чтобы клиент предоставил параметр в конструктор для зависимости.

// КонструкторКлиент(Служба служба) {    // Сохраняем ссылку на переданную службу внутри этого клиента    это.служба = служба;}

Инъекция сеттера

Этот метод требует, чтобы клиент предоставил метод установки для зависимости.

// Метод установкиобщественный пустота setService(Служба служба) {    // Сохраняем ссылку на переданную службу внутри этого клиента.    это.служба = служба;}

Внедрение интерфейса

Это просто клиент, публикующий интерфейс роли для методов установки зависимостей клиента. Его можно использовать, чтобы установить, как инжектор должен разговаривать с клиентом при внедрении зависимостей.

// Интерфейс установщика сервисов.общественный интерфейс ServiceSetter {    общественный пустота setService(Служба служба);}// Клиентский классобщественный учебный класс Клиент орудия ServiceSetter {    // Внутренняя ссылка на службу, используемую этим клиентом.    частный Служба служба;    // Устанавливаем службу, которую должен использовать этот клиент.    @Override    общественный пустота setService(Служба служба) {        это.служба = служба;    }}

Сравнение внедрения конструктора

Предпочтительно, когда сначала могут быть построены все зависимости, потому что это можно использовать для обеспечения того, чтобы клиентский объект всегда находился в допустимом состоянии, в отличие от того, чтобы некоторые из его ссылок на зависимости были пустыми (не были установлены). Однако сам по себе ему не хватает гибкости, чтобы впоследствии изменить его зависимости. Это может быть первым шагом на пути к тому, чтобы клиент неизменный и поэтому потокобезопасный.

// КонструкторКлиент(Служба служба, Служба otherService) {    если (служба == ноль) {        бросать новый InvalidParameterException("услуга не должна быть нулевой");    }    если (otherService == ноль) {        бросать новый InvalidParameterException("otherService не должен быть нулевым");    }    // Сохраняем ссылки на сервисы внутри этого клиента    это.служба = служба;    это.otherService = otherService;}

Сравнение впрыска сеттера

Требует, чтобы клиент предоставил метод установки для каждой зависимости. Это дает свободу манипулировать состоянием ссылок на зависимости в любое время. Это обеспечивает гибкость, но если необходимо внедрить более одной зависимости, клиенту сложно гарантировать, что все зависимости внедряются до того, как клиент может быть предоставлен для использования.

// Устанавливаем сервис, который будет использовать этот клиентобщественный пустота setService(Служба служба) {    если (служба == ноль) {        бросать новый InvalidParameterException("услуга не должна быть нулевой");    }    это.служба = служба;}// Устанавливаем другую службу, которая будет использоваться этим клиентомобщественный пустота setOtherService(Служба otherService) {    если (otherService == ноль) {        бросать новый InvalidParameterException("otherService не должен быть нулевым");    }    это.otherService = otherService;}

Поскольку эти инъекции происходят независимо друг от друга, невозможно определить, когда инжектор завершил подключение клиента. Зависимость может быть оставлена ​​нулевой просто из-за того, что инжектор не может вызвать свой установщик. Это заставляет проверку того, что инъекция была завершена, с момента сборки клиента до момента его использования.

// Устанавливаем сервис, который будет использовать этот клиентобщественный пустота setService(Служба служба) {    это.служба = служба;}// Устанавливаем другую службу, которая будет использоваться этим клиентомобщественный пустота setOtherService(Служба otherService) {    это.otherService = otherService;}// Проверяем ссылки на сервисы этого клиентачастный пустота validateState() {    если (служба == ноль) {        бросать новый IllegalStateException("услуга не должна быть нулевой");    }    если (otherService == ноль) {        бросать новый IllegalStateException("otherService не должен быть нулевым");    }}// Метод, использующий ссылки на службыобщественный пустота сделай что-нибудь() {    validateState();    служба.делай свое дело();    otherService.делай свое дело();}

Сравнение внедрения интерфейса

Преимущество внедрения интерфейса в том, что зависимости могут полностью игнорировать своих клиентов, но все же могут получать ссылку на нового клиента и, используя ее, отправлять клиенту ссылку на себя. Таким образом, зависимости становятся инжекторами. Ключ в том, что метод инъекции (который может быть просто классическим методом установки) предоставляется через интерфейс.

Ассемблер по-прежнему необходим для представления клиента и его зависимостей. Ассемблер берет ссылку на клиента, приводит ее к интерфейсу установщика, который устанавливает эту зависимость, и передает ее этому объекту зависимости, который развернется и передаст клиенту ссылку на себя.

Чтобы внедрение интерфейса имело значение, зависимость должна что-то делать в дополнение к простой передаче ссылки на себя. Он может действовать как фабрика или субассемблер для разрешения других зависимостей, тем самым абстрагируя некоторые детали от основного ассемблера. Это может быть подсчет ссылок, чтобы зависимость знала, сколько клиентов ее используют. Если зависимость поддерживает набор клиентов, позже она может внедрить в них всех с другим экземпляром себя.

// Интерфейс установщика службы.общественный интерфейс ServiceSetter {    общественный пустота setService(Служба служба);}// Клиентский классобщественный учебный класс Клиент орудия ServiceSetter {    // Внутренняя ссылка на службу, используемую этим клиентом.    частный Служба служба;    // Устанавливаем службу, которую должен использовать этот клиент.    @Override    общественный пустота setService(Служба служба) {        это.служба = служба;    }}// Класс инжектораобщественный учебный класс Сервисный инжектор {	Набор<ServiceSetter> клиенты;	общественный пустота вводить(ServiceSetter клиент) {		клиенты.Добавить(клиент);		клиент.setService(новый ServiceFoo());	}	общественный пустота switchToBar() {		за (Клиент клиент : клиенты) {			клиент.setService(новый ServiceBar());		}	}}// Сервисные классыобщественный учебный класс ServiceFoo орудия Служба {}общественный учебный класс ServiceBar орудия Служба {}

Примеры сборки

Ручная сборка в основном вручную - это один из способов реализации внедрения зависимостей.

общественный учебный класс Инжектор {    общественный статический пустота главный(Нить[] аргументы) {        // Сначала строим зависимости        Служба служба = новый ExampleService();        // Внедрение службы в стиле конструктора        Клиент клиент = новый Клиент(служба);        // Используем объекты        Система.из.println(клиент.приветствовать());    }	}

В приведенном выше примере граф объекта создается вручную, а затем вызывается в какой-то момент, чтобы он начал работать. Важно отметить, что этот инжектор не чистый. Он использует один из создаваемых им объектов. Он имеет чисто конструктивные отношения с ExampleService, но смешивает построение и использование Client. Это не должно быть обычным явлением. Однако это неизбежно. Точно так же, как объектно-ориентированному программному обеспечению для начала работы нужен не объектно-ориентированный статический метод, такой как main (), графу внедренных зависимостей требуется как минимум одна (желательно только одна) точка входа, чтобы все это начало.

Ручное построение в основном методе может быть не таким простым и может включать вызов строители, фабрики, или другой строительные модели также. Это может быть довольно сложно и абстрактно. Линия пересечена от ручного внедрения зависимостей к рамки внедрение зависимостей после того, как код построения больше не является индивидуальным для приложения и вместо этого становится универсальным.[39]

Фреймворки вроде Весна может создавать те же объекты и связывать их вместе перед возвратом ссылки клиенту. Все упоминания о конкретной ExampleService можно перенести из кода в данные конфигурации.

импорт org.springframework.beans.factory.BeanFactory;импорт org.springframework.context.ApplicationContext;импорт org.springframework.context.support.ClassPathXmlApplicationContext;общественный учебный класс Инжектор {	общественный статический пустота главный(Нить[] аргументы) {		// - Сборка объектов - //		BeanFactory фасоль = новый ClassPathXmlApplicationContext("Beans.xml");		Клиент клиент = (Клиент) фасоль.getBean("клиент");		// - Использование объектов - //		Система.из.println(клиент.приветствовать());	}}

Такие фреймворки, как Spring, позволяют выводить детали сборки в файлы конфигурации. Этот код (выше) создает объекты и связывает их вместе в соответствии с Beans.xml (ниже). ExampleService все еще создается, хотя он упоминается только ниже. Таким образом можно определить длинный и сложный граф объектов, и единственный класс, упомянутый в коде, будет иметь метод точки входа, которым в данном случае является greet ().

 <?xml version="1.0" encoding="UTF-8"?> <бобы xmlns ="http://www.springframework.org/schema/beans"  xmlns: xsi ="http://www.w3.org/2001/XMLSchema-instance"  xsi: schemaLocation ="http://www.springframework.org/schema/beans  http://www.springframework.org/schema/beans/spring-beans-3.0.xsd ">    <фасоль id ="служба" class ="ExampleService">    </bean>    <фасоль id ="клиент" class =«Клиент»>        <конструктор-аргумент значение ="служба" />            </bean></beans>

В приведенном выше примере Клиент и Служба не претерпели никаких изменений, чтобы быть предоставленными к весне. Им разрешено оставаться простыми POJO.[40][41][42] Это показывает, как Spring может соединять сервисы и клиентов, которые совершенно не знают о его существовании. Этого нельзя было бы сказать, если бы к классам добавлялись весенние аннотации. Сохраняя специфичные для Spring аннотации и вызовы от распространения среди многих классов, система остается лишь слабо зависимой от Spring.[33] Это может быть важно, если система намеревается пережить весну.

Решение поддерживать чистоту POJO не обходится без затрат. Вместо того, чтобы тратить усилия на разработку и обслуживание сложных файлов конфигурации, можно просто использовать аннотации для пометки классов и позволить Spring сделать остальную работу. Устранение зависимостей может быть простым, если они следуют соглашению, например, по типу или по имени. Это выбор соглашение важнее конфигурации.[43] Также можно утверждать, что при рефакторинге под другую платформу удаление аннотаций, специфичных для платформы, было бы тривиальной частью задачи.[44] и многие аннотации для инъекций теперь стандартизированы.[45][46]

импорт org.springframework.beans.factory.BeanFactory;импорт org.springframework.context.ApplicationContext;импорт org.springframework.context.annotation.AnnotationConfigApplicationContext;общественный учебный класс Инжектор {	общественный статический пустота главный(Нить[] аргументы) {		// Собираем объекты		BeanFactory фасоль = новый AnnotationConfigApplicationContext(MyConfiguration.учебный класс);		Клиент клиент = фасоль.getBean(Клиент.учебный класс);		// Используем объекты		Система.из.println(клиент.приветствовать());	}}
импорт org.springframework.context.annotation.Bean;импорт org.springframework.context.annotation.ComponentScan;импорт org.springframework.context.annotation.Configuration;@ComponentScanобщественный учебный класс MyConfiguration {    @Bean    общественный Клиент клиент(ExampleService служба) {        возвращаться новый Клиент(служба);    }}
@Компонентобщественный учебный класс ExampleService {    общественный Нить getName() {        возвращаться "Мир!";    }}

Сравнение сборок

Различные реализации инжекторов (фабрики, локаторы услуг, и контейнеры для внедрения зависимостей) не сильно отличаются в том, что касается внедрения зависимостей. Вся разница в том, где их можно использовать.Переместите вызовы к фабрике или локатору сервисов из клиента в main, и вдруг main становится довольно хорошим контейнером для внедрения зависимостей.

Убрав все знания об инъекторе, чистый клиент, свободный от знаний о внешнем мире, останется позади. Однако любой объект, использующий другие объекты, может считаться клиентом. Объект, содержащий main, не исключение. Этот основной объект не использует внедрение зависимостей. Фактически он использует шаблон локатора сервисов. Этого нельзя избежать, потому что выбор реализаций сервиса нужно где-то делать.

Внешний перенос зависимостей в файлы конфигурации не меняет этого факта. Что делает эту реальность частью хорошего дизайна, так это то, что локатор сервисов не распространяется по всей кодовой базе. Он ограничен одним местом для каждого приложения. Это оставляет остальную часть кода свободной для использования внедрения зависимостей для создания чистых клиентов.

Шаблон внедрения зависимостей

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

общественный статический пустота главный(Нить[] аргументы) бросает IOException {    // Строительный код.     Приветствующий встречающий = новый Приветствующий(Система.из); // Это может быть много линий, которые соединяют множество объектов        // Код поведения.    встречающий.приветствовать(); // Это один вызов одного метода для одного объекта в графе объектов}учебный класс Приветствующий {    общественный пустота приветствовать() {        это.из.println("Привет, мир!");    }    общественный Приветствующий(PrintStream из) {        это.из = из;    }    частный PrintStream из;}

Пример AngularJS

в AngularJS framework, есть только три способа, которыми компонент (объект или функция) может напрямую обращаться к своим зависимостям:

  1. Компонент может создавать зависимость, обычно используя новый оператор.
  2. Компонент может искать зависимость, обращаясь к глобальной переменной.
  3. Компоненту может быть передана зависимость там, где это необходимо.

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

Третий вариант является наиболее жизнеспособным, поскольку он снимает с компонента ответственность за обнаружение зависимости. Зависимость просто передается компоненту.

функция SomeClass(встречающий) {  это.встречающий = встречающий;}SomeClass.прототип.сделай что-нибудь = функция(имя) {  это.встречающий.приветствовать(имя);}

В приведенном выше примере SomeClass не занимается созданием или поиском зависимости приветствующего, он просто передается вызывающему при создании экземпляра.

Это желательно, но налагает ответственность за зависимость от кода, который создает SomeClass.

Чтобы управлять ответственностью за создание зависимостей, каждое приложение AngularJS имеет инжектор. Инжектор - это сервисный локатор, который отвечает за построение и поиск зависимостей.

Вот пример использования сервиса инжектора:

// Предоставляем информацию о проводке в модулевар myModule = угловатый.модуль('myModule', []);// Обучаем инжектора, как создать службу приветствия. // программа приветствия зависит от службы $ window. // Служба приветствия - это объект, который// содержит метод приветствия.myModule.фабрика('приветствующий', функция($ окно) {  возвращаться {    приветствовать: функция(текст) {      $ окно.тревога(текст);    }  };});

Создайте новый инжектор, который может предоставлять компоненты, определенные в myModule модуль и запросите услуги приветствия от инжектора. (Обычно это делается автоматически при загрузке AngularJS).

вар инжектор = угловатый.инжектор(['myModule', 'нг']);вар встречающий = инжектор.получать('приветствующий');

Запрос зависимостей решает проблему жесткого кодирования, но это также означает, что инжектор необходимо передавать по всему приложению. Прохождение форсунки нарушает Закон Деметры. Чтобы исправить это, мы используем декларативную нотацию в наших HTML-шаблонах, чтобы передать ответственность за создание компонентов инжектору, как в этом примере:

<div ng-контроллер=«MyController»>  <кнопка нг-щелчок="скажи привет()">Привет</кнопка></div>
функция MyController($ scope, встречающий) {  $ scope.скажи привет = функция() {    встречающий.приветствовать('Привет, мир');  };}

Когда AngularJS компилирует HTML, он обрабатывает ng-контроллер директива, которая, в свою очередь, просит инжектор создать экземпляр контроллера и его зависимости.

injector.instantiate (MyController);

Все это делается за кадром. Поскольку ng-контроллер подчиняется инжектору для создания экземпляра класса, он может удовлетворить все зависимости MyController без ведома контроллера о форсунке. Код приложения просто объявляет необходимые ему зависимости без необходимости иметь дело с инжектором. Эта установка не нарушает Закон Деметры.

C #

Пример Внедрение конструктора, Инъекция сеттера и Внедрение интерфейса на C #

с помощью Система;пространство имен Внедрение зависимости{    // Интерфейс библиотеки    интерфейс IGamepadФункциональность    {        Нить GetGamepadName();        пустота SetVibrationPower(плавать В силе);    }    // Конкретная реализация функциональности контроллера xbox    учебный класс XBoxГеймпад : IGamepadФункциональность    {        только чтение Нить GamepadName = «Контроллер XBox»;        плавать Вибрация = 1.0f;        общественный Нить GetGamepadName() => GamepadName;        общественный пустота SetVibrationPower(плавать В силе) => Вибрация = Математика.Зажим(В силе, 0,0f, 1.0f);    }    // Конкретная реализация функциональности контроллера playstation    учебный класс PlayStationДжойстик : IGamepadФункциональность    {        только чтение Нить ControllerName = «Контроллер PlayStation»;        плавать Вибрация = 100.0f;        общественный Нить GetGamepadName() => ControllerName;        общественный пустота SetVibrationPower(плавать В силе) => Вибрация = Математика.Зажим(В силе * 100.0f, 0,0f, 100.0f);    }    // Конкретная реализация функционала парового регулятора    учебный класс SteamController : IGamepadФункциональность    {        только чтение Нить JoystickName = «Паровой контроллер»;        двойной Вибрирующий = 1.0;        общественный Нить GetGamepadName() => JoystickName;        общественный пустота SetVibrationPower(плавать В силе) => Вибрирующий = Конвертировать.Удвоить(Математика.Зажим(В силе, 0,0f, 1.0f));    }    // Интерфейс для внедрения функциональности геймпада    интерфейс IGamepadФункциональностьИнжектор    {        пустота InjectFunctionality(IGamepadФункциональность InGamepadФункциональность);    }    учебный класс CGamepad : IGamepadФункциональностьИнжектор    {        IGamepadФункциональность _ГеймпадФункциональность;        общественный CGamepad()        {        }        // Внедрение конструктора        общественный CGamepad(IGamepadФункциональность InGamepadФункциональность) => _ГеймпадФункциональность = InGamepadФункциональность;        // Инъекция сеттера        общественный пустота SetGamepadФункциональность(IGamepadФункциональность InGamepadФункциональность) => _ГеймпадФункциональность = InGamepadФункциональность;        // Внедрение интерфейса        общественный пустота InjectFunctionality(IGamepadФункциональность InGamepadФункциональность) => _ГеймпадФункциональность = InGamepadФункциональность;        общественный пустота Витрина()        {            Нить Сообщение = Нить.Формат("Мы используем {0} прямо сейчас, вы хотите изменить мощность вибрации?  R  n", _ГеймпадФункциональность.GetGamepadName());            Консоль.WriteLine(Сообщение);        }    }    перечислить EP-платформы: байт    {        Xbox,        Игровая приставка,        Пар    }    учебный класс CGameEngine    {        EP-платформы _Платформа;        CGamepad _Геймпад;        общественный пустота SetPlatform(EP-платформы InPlatform)        {            _Платформа = InPlatform;            выключатель(_Платформа)            {                дело EP-платформы.Xbox:                    // внедряет зависимость от класса XBoxGamepad через Constructor Injection                    _Геймпад = новый CGamepad(новый XBoxГеймпад());                    перемена;                дело EP-платформы.Игровая приставка:                    _Геймпад = новый CGamepad();                    // внедряет зависимость от класса PlaystationJoystick через Setter Injection                    _Геймпад.SetGamepadФункциональность(новый PlayStationДжойстик());                    перемена;                дело EP-платформы.Пар:                    _Геймпад = новый CGamepad();                    // внедряет зависимость от класса SteamController через внедрение интерфейса                    _Геймпад.InjectFunctionality(новый SteamController());                    перемена;            }            _Геймпад.Витрина();        }    }    учебный класс Программа    {        статический пустота Главный(нить[] аргументы)        {            Консоль.WriteLine("Привет, мир!");                        CGameEngine Двигатель = новый CGameEngine();            Двигатель.SetPlatform(EP-платформы.Пар);            Двигатель.SetPlatform(EP-платформы.Xbox);            Двигатель.SetPlatform(EP-платформы.Игровая приставка);        }    }}

Смотрите также

Рекомендации

  1. ^ I.T., Титан. «Джеймс Шор: демистификация инъекции зависимости». www.jamesshore.com. Получено 2015-07-18.
  2. ^ "Голливудский принцип". c2.com. Получено 2015-07-19.
  3. ^ «Шаблон проектирования внедрения зависимостей - проблема, решение и применимость». w3sDesign.com. Получено 2017-08-12.
  4. ^ Эрих Гамма, Ричард Хелм, Ральф Джонсон, Джон Влиссидес (1994). Паттерны проектирования: элементы объектно-ориентированного программного обеспечения многократного использования. Эддисон Уэсли. стр.87ff. ISBN  0-201-63361-2.CS1 maint: несколько имен: список авторов (связь)
  5. ^ а б Симан, Марк (октябрь 2011 г.). Внедрение зависимостей в .NET. Публикации Мэннинга. п. 4. ISBN  9781935182504.
  6. ^ «Внедрение зависимостей в NET» (PDF). philkildea.co.uk. п. 4. Получено 2015-07-18.
  7. ^ «Как объяснить инъекцию зависимости 5-летнему ребенку?». stackoverflow.com. Получено 2015-07-18.
  8. ^ Seemann, Марк. «Внедрение зависимостей - слабая связь». blog.ploeh.dk. Получено 2015-07-28.
  9. ^ Нико Шварц, Мирча Лунгу, Оскар Нирстраз, «Сьюз: разделение обязанностей и статических методов для точной настройки», Журнал объектных технологий, Том 11, №2. 1 (апрель 2012 г.), стр. 3: 1-23
  10. ^ «Передача информации методу или конструктору (Учебники Java ™> Изучение языка Java> Классы и объекты)». docs.oracle.com. Получено 2015-07-18.
  11. ^ «Карри принципа инверсии зависимостей (DIP), инверсии управления (IoC), внедрения зависимостей (DI) и контейнера IoC - CodeProject». www.codeproject.com. Получено 2015-08-08.
  12. ^ «Как заставить» программу работать с интерфейсом «без использования java-интерфейса в java 1.6». programmers.stackexchange.com. Получено 2015-07-19.
  13. ^ «К« новому »или не к« новому »…». Получено 2015-07-18.
  14. ^ «Как написать тестируемый код». www.loosecouplings.com. Получено 2015-07-18.
  15. ^ «Написание чистого, проверяемого кода». www.ethanresnick.com. Получено 2015-07-18.
  16. ^ Сирони, Джорджио. «Когда делать инъекции: различие между новыми и инъекционными препаратами - невидимо для глаза». www.giorgiosironi.com. Получено 2015-07-18.
  17. ^ «Инверсия управления против внедрения зависимостей». stackoverflow.com. Получено 2015-08-05.
  18. ^ «В чем разница между шаблоном стратегии и внедрением зависимостей?». stackoverflow.com. Получено 2015-07-18.
  19. ^ «Внедрение зависимостей! = С использованием контейнера DI». www.loosecouplings.com. Получено 2015-07-18.
  20. ^ «Черная овца» DIY-DI »Печать». blacksheep.parry.org. Архивировано из оригинал на 2015-06-27. Получено 2015-07-18.
  21. ^ https://python.astrotech.io/design-patterns/structural/dependency-injection.html
  22. ^ http://python-dependency-injector.ets-labs.org/introduction/di_in_python.html
  23. ^ https://visualstudiomagazine.com/articles/2014/07/01/larger-applications.aspx
  24. ^ а б «Программа Java Community Process (SM) - JSR: запросы спецификации Java - деталь JSR № 330». jcp.org. Получено 2015-07-18.
  25. ^ https://dzone.com/articles/how-dependency-injection-di-works-in-spring-java-a
  26. ^ "городской канук, а: о внедрении зависимостей и нарушениях инкапсуляции". www.bryancook.net. Получено 2015-07-18.
  27. ^ «Шаблон проектирования внедрения зависимостей». msdn.microsoft.com. Получено 2015-07-18.
  28. ^ https://dzone.com/articles/how-dependency-injection-di-works-in-spring-java-a
  29. ^ https://dzone.com/articles/how-dependency-injection-di-works-in-spring-java-a
  30. ^ https://www.freecodecamp.org/news/a-quick-intro-to-dependency-injection-what-it-is-and-when-to-use-it-7578c84fa88f/
  31. ^ https://www.professionalqa.com/dependency-injection
  32. ^ а б «Каковы недостатки использования внедрения зависимостей?». stackoverflow.com. Получено 2015-07-18.
  33. ^ а б «Инверсия внедрения зависимостей - чистый кодер». sites.google.com. Получено 2015-07-18.
  34. ^ «Отделение вашего приложения от инфраструктуры внедрения зависимостей». InfoQ. Получено 2015-07-18.
  35. ^ "Шаблон проектирования внедрения зависимостей - структура и взаимодействие". w3sDesign.com. Получено 2017-08-12.
  36. ^ Мартин Фаулер (23 января 2004 г.). «Инверсия управляющих контейнеров и шаблон внедрения зависимостей - формы внедрения зависимостей». Martinfowler.com. Получено 2014-03-22.
  37. ^ «Ян - Типы внедрения зависимостей». Yan.codehaus.org. Архивировано из оригинал на 2013-08-18. Получено 2013-12-11.
  38. ^ «AccessibleObject (Java Platform SE 7)». docs.oracle.com. Получено 2015-07-18.
  39. ^ Риле, Дирк (2000), Дизайн каркаса: подход к ролевому моделированию (PDF), Швейцарский федеральный технологический институт
  40. ^ "Весенние советы: POJO с аннотациями не является простым". Архивировано из оригинал на 2015-07-15. Получено 2015-07-18.
  41. ^ «Аннотации в POJO - благо или проклятие? | Techtracer». 2007-04-07. Получено 2015-07-18.
  42. ^ Динамические модули Pro Spring для сервисных платформ OSGi. АПресс. 2009-02-17. ISBN  9781430216124. Получено 2015-07-06.
  43. ^ "Блог Captain Debug:" Соглашение по настройке "заходит слишком далеко?". www.captaindebug.com. Получено 2015-07-18.
  44. ^ Декер, Колин. "В чем проблема с @Inject? | Колин в журнале разработчиков". blog.cgdecker.com. Получено 2015-07-18.
  45. ^ Морлинг, Гуннар (18 ноября 2012 г.). «Кинжал - новая среда внедрения зависимостей Java». Dagger - новая структура внедрения зависимостей Java - размышления программиста. Получено 2015-07-18.
  46. ^ «Программа Java Community Process (SM) - JSR: запросы спецификации Java - деталь JSR № 330». www.jcp.org. Получено 2015-07-18.

внешняя ссылка