Я читаю о внедрении зависимости (DI). Для меня это очень сложная вещь, так как я читал, что она также ссылается на инверсию управления (IoC), и я чувствовал, что собираюсь отправиться в путешествие.
Это мое понимание: вместо того, чтобы создавать модель в классе, который также потребляет ее, вы передаете (внедряете) модель (уже заполненную интересными свойствами) туда, где она необходима (в новый класс, который может принять ее в качестве параметра в конструктор).
Для меня это просто передача аргумента. Должно быть, мисс поняла суть? Может быть, это станет более очевидным с большими проектами?
Мое понимание не DI (используя псевдокод):
public void Start()
{
MyClass class = new MyClass();
}
...
public MyClass()
{
this.MyInterface = new MyInterface();
}
И DI будет
public void Start()
{
MyInterface myInterface = new MyInterface();
MyClass class = new MyClass(myInterface);
}
...
public MyClass(MyInterface myInterface)
{
this.MyInterface = myInterface;
}
Может ли кто-то пролить свет, я уверен, что я в замешательстве здесь.
dependency-injection
MyDaftQuestions
источник
источник
Ответы:
Да, вы вводите свои зависимости либо через конструктор, либо через свойства.
Одна из причин этого - не загружать MyClass деталями того, как должен быть создан экземпляр MyInterface. MyInterface может быть чем-то, что само по себе имеет целый список зависимостей, и код MyClass стал бы очень уродливым, если бы вы создали все зависимости MyInterface внутри MyClass.
Еще одна причина для тестирования.
Если у вас есть зависимость от интерфейса средства чтения файлов, и вы вводите эту зависимость через конструктор, скажем, в ConsumerClass, это означает, что во время тестирования вы можете передать реализацию средства чтения файлов в памяти в ConsumerClass, избегая необходимости сделать I / O во время тестирования.
источник
Как DI может быть реализован, очень сильно зависит от используемого языка.
Вот простой пример без DI:
Это отстой, например, когда для теста я хочу использовать фиктивный объект
bar
. Таким образом, мы можем сделать это более гибким и разрешить передачу экземпляров через конструктор:Это уже самая простая форма внедрения зависимости. Но это все равно отстой, потому что все должно быть сделано вручную (также, потому что вызывающая сторона может хранить ссылку на наши внутренние объекты и, таким образом, делать недействительным наше состояние).
Мы можем ввести больше абстракций, используя фабрики:
Один из вариантов -
Foo
генерировать абстрактную фабрикуДругой вариант - передать фабрику конструктору:
Остающиеся проблемы с этим заключаются в том, что нам нужно написать новый
DependencyManager
подкласс для каждой конфигурации, и что количество зависимостей, которыми можно управлять, довольно ограничено (каждая новая зависимость требует нового метода в интерфейсе).С такими функциями, как отражение и динамическая загрузка классов, мы можем обойти это. Но это очень сильно зависит от используемого языка. В Perl на классы можно ссылаться по их имени, и я мог бы просто сделать
В таких языках, как Java, менеджер зависимостей может вести себя подобно
Map<Class, Object>
:Для какого фактического класса
Bar.class
разрешается, может быть сконфигурирован во время выполнения, например, поддерживая,Map<Class, Class>
который сопоставляет интерфейсы с реализациями.В написании конструктора все еще есть элемент ручного управления. Но мы можем сделать конструктор совершенно ненужным, например, управляя DI с помощью аннотаций:
Вот пример реализации крошечной (и очень ограниченной) структуры DI: http://ideone.com/b2ubuF , хотя эта реализация полностью непригодна для неизменяемых объектов (эта наивная реализация не может принимать никаких параметров для конструктора).
источник
У наших друзей на Stack Overflow есть хороший ответ на этот вопрос . Мой любимый второй ответ, включая цитату:
из блога Джеймса Шора . Конечно, есть более сложные версии / шаблоны, наложенные поверх этого, но этого достаточно, чтобы в основном понять, что происходит. Вместо объекта, создающего свои собственные переменные экземпляра, они передаются извне.
источник
Допустим, вы делаете дизайн интерьера в своей гостиной: вы устанавливаете необычную люстру на потолке и подключаете стильный подходящий торшер. Год спустя твоя любимая жена решает, что ей понравится комната менее формальная. Какой светильник будет легче заменить?
Идея DI состоит в том, чтобы использовать подход «плагин» везде. Это может показаться не таким уж большим делом, если вы живете в простой лачуге без гипсокартона и всех электрических проводов. (Вы разнорабочий - вы можете изменить что угодно!) И точно так же, в небольшом и простом приложении DI может добавить больше сложностей, чем оно того стоит.
Но для большого и сложного применения DI незаменим для длительного обслуживания. Это позволяет вам «отключать» целые модули от вашего кода (например, серверной части базы данных) и обмениваться ими с разными модулями, которые выполняют одно и то же, но делают это совершенно по-другому (например, облачная система хранения).
Кстати, обсуждение DI было бы неполным без рекомендации Маркса Симана о внедрении зависимостей в .NET . Если вы знакомы с .NET и когда-либо намеревались участвовать в крупномасштабной разработке ПО, это очень важно. Он объясняет эту концепцию гораздо лучше, чем я.
Позвольте мне оставить вас с последней важной характеристикой DI, которую пропускает ваш пример кода. DI позволяет вам использовать огромный потенциал гибкости, который воплощен в поговорке « Программа для интерфейсов ». Чтобы немного изменить ваш пример:
Но СЕЙЧАС, поскольку
MyRoom
он предназначен для интерфейса ILightFixture , я могу легко перейти в следующий год и изменить одну строку в функции Main («вставить» другую «зависимость»), и я сразу получу новую функциональность в MyRoom (без необходимости перестройте или повторно разверните MyRoom, если он находится в отдельной библиотеке. Это, конечно, предполагает, что все ваши реализации ILightFixture разработаны с учетом подстановки Лискова). Все, с помощью простого изменения:Кроме того , я могу теперь Test Unit
MyRoom
(вы это модульное тестирование, не так ли?) И использовать «ложный» светильник, который позволяет мне проверитьMyRoom
полностью независимым отClassyChandelier.
Конечно, есть еще много преимуществ, но именно они продали эту идею мне.
источник
Внедрение зависимостей - это просто передача параметра, но это все еще приводит к некоторым проблемам, и имя призвано немного сфокусироваться на том, почему разница между созданием объекта и передачей его важна.
Это важно, потому что (довольно очевидно) всякий раз, когда объект A вызывает тип B, A зависит от того, как работает B. Это означает, что если A принимает решение о том, какой конкретно тип B он будет использовать, то большая гибкость в том, как A может использоваться внешними классами без изменения A, была потеряна.
Я упоминаю об этом, потому что вы говорите о том, что «упускаете из виду» внедрение зависимости, и именно поэтому люди любят это делать. Это может означать просто передачу параметра, но важно ли это сделать.
Кроме того, некоторые из трудностей в реализации DI лучше проявляются в больших проектах. Обычно вы перемещаете фактическое решение о том, какие конкретные классы использовать, до самого верха, поэтому ваш метод start может выглядеть следующим образом:
но с еще 400 строками такого рода захватывающего кода. Если вы представляете себе, что со временем привлекательность контейнеров DI становится более очевидной.
Кроме того, представьте класс, который, скажем, реализует IDisposable. Если классы, использующие его, получают его с помощью инъекции, а не создают его самостоятельно, как они узнают, когда вызывать Dispose ()? (Это еще одна проблема, с которой сталкивается контейнер DI.)
Так что, конечно, внедрение зависимостей - это просто передача параметра, но на самом деле, когда люди говорят, что внедрение зависимостей означает «передачу параметра в ваш объект вместо альтернативы, когда ваш объект сам что-то создает,» и устранение всех преимуществ недостатков: такой подход, возможно, с помощью контейнера DI.
источник
На уровне класса это легко.
«Внедрение зависимостей» просто отвечает на вопрос «как я найду моих сотрудников»: «Они на тебя давят - тебе не нужно идти и брать их самому». (Это похоже - но не то же самое, что и «Инверсия контроля», где на вопрос «как я упорядочу свои операции над своими входами?» Есть аналогичный ответ).
Только выгоды , которые имеют ваши сотрудники толкнули вас, что позволяет клиентский коду использовать класс , чтобы составить граф объекта , который соответствует его нынешней потребности ... Вы не произвольно предварительно определили форму и переменчивость графы приватно принятия решения конкретные типы и жизненный цикл ваших сотрудников.
(Все эти другие преимущества, связанные с тестируемостью, слабой связью и т. Д., В значительной степени вытекают из использования интерфейсов, а не столько из-за внедрения зависимости, хотя DI естественным образом способствует использованию интерфейсов).
Стоит отметить, что, если вы избегаете создания экземпляров своих собственных соавторов, ваш класс должен поэтому получать своих соавторов из конструктора, свойства или аргумента метода (кстати, этот последний параметр часто упускается из виду ... он делает не всегда имеет смысл, чтобы класс «соавторы» был частью его «состояния»).
И это хорошо.
На уровне приложения ...
Так много для индивидуального взгляда на вещи. Допустим, у вас есть куча классов, которые следуют правилу «не создавайте экземпляры ваших собственных соавторов» и хотите создать из них приложение. Самое простое, что можно сделать - это использовать старый добрый код (действительно полезный инструмент для вызова конструкторов, свойств и методов!), Чтобы составить нужный вам граф объектов и добавить к нему некоторую информацию. (Да, некоторые из этих объектов в вашем графе сами по себе будут объектными фабриками, которые были переданы в качестве соавторов другим долгоживущим объектам в графе, готовым к работе ... вы не можете предварительно построить каждый объект! ).
... вам нужно «гибко» настраивать объект-граф вашего приложения ...
В зависимости от ваших других (не связанных с кодом) целей, вы можете захотеть дать конечному пользователю некоторый контроль над развернутым графом объектов. Это ведет вас в направлении схемы конфигурации того или иного типа, будь то текстовый файл вашего собственного дизайна с некоторыми парами имя / значение, файл XML, пользовательский DSL, декларативный язык описания графов, такой как YAML, императивный язык сценариев, такой как JavaScript, или что-то еще, соответствующее поставленной задаче. Все, что нужно для составления правильного графа объектов, таким образом, чтобы удовлетворить потребности ваших пользователей.
... может быть значительным конструктивным фактором.
В самых экстремальных обстоятельствах этого типа вы можете выбрать очень общий подход и дать конечным пользователям общий механизм для «привязки» объекта-графа по своему выбору и даже позволить им предоставлять конкретные реализации интерфейсов для во время выполнения! (Ваша документация - блестящая жемчужина, ваши пользователи очень умны, знакомы, по крайней мере, с грубым контуром графа объектов вашего приложения, но у вас нет удобного компилятора). Этот сценарий теоретически может возникнуть в некоторых «корпоративных» ситуациях.
В этом случае у вас, вероятно, есть декларативный язык, который позволяет вашим пользователям выражать любой тип, композицию графов объектов таких типов и палитру интерфейсов, которые мифический конечный пользователь может смешивать и сопоставлять. Чтобы уменьшить когнитивную нагрузку на ваших пользователей, вы предпочитаете подход «конфигурация по соглашению», так что им нужно только вмешаться и обойти интересующий объект-граф-фрагмент, а не бороться со всем этим.
Вы бедняга!
Поскольку вам не хотелось писать все это самостоятельно (а если серьезно, проверьте привязку YAML для вашего языка), вы используете какую-то инфраструктуру DI.
В зависимости от зрелости этой среды у вас может не быть возможности использовать конструктор-инъекцию, даже когда это имеет смысл (соавторы не меняются в течение срока службы объекта), что вынуждает вас использовать Setter Injection (даже когда коллабораторы не изменяются в течение всего срока службы объекта, и даже если на самом деле нет логической причины, по которой все конкретные реализации интерфейса должны иметь коллабораторов определенного типа). Если это так, то вы в настоящее время находитесь в аду сильной связи, несмотря на то, что старательно «использовали интерфейсы» по всей вашей кодовой базе - ужас!
Надеюсь, однако, что вы использовали DI-фреймворк, который дает вам возможность внедрения конструктора, и ваши пользователи лишь немного раздражительны из- за того, что вы не тратите больше времени на размышления о конкретных вещах, которые им нужны для настройки, и предоставления им пользовательского интерфейса, более подходящего для этой задачи. под рукой. (Хотя, если быть честным, вы, вероятно, пытались придумать способ, но JavaEE подвела вас, и вам пришлось прибегнуть к этому ужасному хаку).
Bootnote
Ни в коем случае вы никогда не используете Google Guice, который дает вам кодеру способ избавиться от задачи составления графов объектов с помощью кода ... путем написания кода. Argh!
источник
Многие люди говорили, что DI - это механизм для инициализации переменных экземпляра.
Для очевидных и простых примеров вам не нужно «внедрение зависимостей». Вы можете сделать это с помощью простой передачи параметров конструктору, как вы отметили в вопросе.
Тем не менее, я думаю, что специальный механизм «внедрения зависимостей» полезен, когда вы говорите о ресурсах уровня приложения, когда и вызывающий, и вызываемый не знают ничего об этих ресурсах (и не хотят знать!).
Например: соединение с базой данных, контекст безопасности, средство ведения журнала, файл конфигурации и т. Д.
Более того, в целом каждый синглтон является хорошим кандидатом на DI. Чем больше из них вы используете и используете, тем больше у вас шансов на DI.
Для таких ресурсов совершенно очевидно, что не имеет смысла перемещать их все через списки параметров, и поэтому DI был изобретен.
Последнее замечание, вам также нужен контейнер DI, который будет выполнять фактическую инъекцию ... (опять же, чтобы освободить как вызывающую, так и вызываемую информацию от деталей реализации).
источник
Если вы передадите абстрактный ссылочный тип, то вызывающий код может передать любой объект, который расширяет / реализует абстрактный тип. Таким образом, в будущем класс, который использует объект, мог бы использовать новый объект, который был создан без изменения его кода.
источник
Это еще один вариант в дополнение к ответу амона
Используйте Builders:
Это то, что я использую для зависимостей. Я не использую DI-фреймворк. Они мешают.
источник