Платформы внедрения зависимостей, такие как Google Guice, дают следующую мотивацию для их использования ( источник ):
Чтобы построить объект, вы сначала строите его зависимости. Но для построения каждой зависимости вам нужны ее зависимости и так далее. Поэтому, когда вы строите объект, вам действительно нужно построить граф объектов.
Создание графов объектов вручную является трудоемким (...) и затрудняет тестирование.
Но я не куплю этот аргумент: даже без инфраструктуры внедрения зависимостей я могу писать классы, которые легко создавать и тестировать. Например, пример со страницы мотивации Guice можно переписать следующим образом:
class BillingService
{
private final CreditCardProcessor processor;
private final TransactionLog transactionLog;
// constructor for tests, taking all collaborators as parameters
BillingService(CreditCardProcessor processor, TransactionLog transactionLog)
{
this.processor = processor;
this.transactionLog = transactionLog;
}
// constructor for production, calling the (productive) constructors of the collaborators
public BillingService()
{
this(new PaypalCreditCardProcessor(), new DatabaseTransactionLog());
}
public Receipt chargeOrder(PizzaOrder order, CreditCard creditCard)
{
...
}
}
Таким образом, могут быть другие аргументы для структур внедрения зависимостей ( которые выходят за рамки этого вопроса !), Но легкое создание графов тестируемых объектов не является одним из них, не так ли?
new ShippingService()
.Ответы:
Существует давняя, давняя дискуссия о том, как сделать инъекцию зависимости наилучшим образом.
Исходный фрагмент Spring создавал простой объект, а затем вводил зависимости через методы установки.
Но затем большое количество людей настаивали на том, что внедрение зависимостей через параметры конструктора было правильным способом сделать это.
Затем, в последнее время, когда использование рефлексии стало более распространенным, установка значений приватных членов напрямую, без установщиков или аргументов конструктора, стала яростью.
Итак, ваш первый конструктор соответствует второму подходу к внедрению зависимостей. Это позволяет вам делать приятные вещи, такие как инжект-макеты для тестирования.
Но у конструктора без аргументов есть эта проблема. Поскольку он создает экземпляры классов реализации для
PaypalCreditCardProcessor
иDatabaseTransactionLog
, он создает жесткую зависимость во время компиляции от PayPal и базы данных. Он берет на себя ответственность за правильное построение и настройку всего этого дерева зависимостей.Представьте себе, что процессор PayPay представляет собой действительно сложную подсистему и дополнительно включает в себя множество вспомогательных библиотек. Создавая зависимость времени компиляции от этого класса реализации, вы создаете неразрывную ссылку на все это дерево зависимостей. Сложность вашего графа объектов только что подскочила на порядок, может быть, на два.
Многие из этих элементов в дереве зависимостей будут прозрачными, но многие из них также должны быть созданы. Скорее всего, вы не сможете просто создать экземпляр
PaypalCreditCardProcessor
.Помимо создания экземпляров, каждому из объектов потребуются свойства, примененные из конфигурации.
Если у вас есть только зависимость от интерфейса, и вы позволяете внешней фабрике создавать и внедрять зависимость, то вы отрубаете все дерево зависимостей PayPal, и сложность вашего кода останавливается на интерфейсе.
Есть и другие преимущества, такие как указание классов реализации в конфигурации (т. Е. Во время выполнения, а не во время компиляции) или наличие более динамической спецификации зависимостей, которая, например, зависит от среды (тестирование, интеграция, производство).
Например, допустим, что у PayPalProcessor было 3 зависимых объекта, а у каждой из этих зависимостей было еще два. И все эти объекты должны получить свойства из конфигурации. Код «как есть» взял бы на себя ответственность за построение всего этого, настройку свойств из конфигурации и т. Д. И т. Д. - все заботы, о которых позаботится структура DI.
Поначалу может показаться неочевидным то, от чего вы себя защищаете с помощью DI-фреймворка, но это накапливается и становится болезненно очевидным со временем. (лол, я говорю из опыта того, что пытался сделать это трудным путем)
...
На практике, даже для очень маленькой программы я нахожу, что пишу в стиле DI и разбиваю классы на пары реализации / фабрики. То есть, если я не использую DI-фреймворк, такой как Spring, я просто собираю несколько простых фабричных классов.
Это обеспечивает разделение задач, так что мой класс может просто делать свое дело, а фабричный класс берет на себя ответственность за сборку и настройку вещей.
Не обязательный подход, но FWIW
...
В более общем смысле шаблон DI / interface уменьшает сложность вашего кода, выполняя две вещи:
абстрагирование нижестоящих зависимостей в интерфейсы
«поднятие» вышестоящих зависимостей из вашего кода в какой-то контейнер
Кроме того, поскольку создание экземпляров и конфигурирование объектов - довольно знакомая задача, структура DI может добиться значительной экономии за счет масштабирования за счет стандартизированной записи и использования таких приемов, как отражение. Рассеяние тех же самых проблем вокруг классов заканчивает тем, что добавило намного больше беспорядка, чем можно было бы подумать.
источник
Связывая ваш граф объектов на раннем этапе процесса, т. Е. Вставляя его в код, вы требуете наличия и контракта, и реализации. Если кто-то еще (возможно, даже вы) хочет использовать этот код для немного другой цели, он должен пересчитать весь граф объектов и переопределить его.
Каркасы DI позволяют вам брать кучу компонентов и соединять их вместе во время выполнения. Это делает систему «модульной», состоящей из ряда модулей, которые работают с интерфейсами друг друга, а не с реализациями друг друга.
источник
Подход «создания экземпляров моих собственных сотрудников» может работать для деревьев зависимостей , но он, безусловно, не будет работать хорошо для графов зависимостей, которые являются ациклическими графами общего назначения (DAG). В DAG зависимости несколько узлов могут указывать на один и тот же узел, что означает, что два объекта используют один и тот же объект в качестве соавтора. Этот случай на самом деле не может быть построен с помощью подхода, описанного в вопросе.
Если некоторые из моих соавторов (или соавторы соавтора) захотят поделиться определенным объектом, мне нужно создать экземпляр этого объекта и передать его моим соавторам. Так что на самом деле мне нужно знать больше, чем мои непосредственные сотрудники, и это, очевидно, не масштабируется.
источник
UnitOfWork
который имеет единственноеDbContext
и несколько хранилищ. Все эти репозитории должны использовать один и тот жеDbContext
объект. С предложенным OP «самоопределением» это становится невозможным.Я не использовал Google Guice, но затратил много времени на перенос старых унаследованных приложений N-уровня в .Net на IoC-архитектуры, такие как Onion Layer, которые зависят от внедрения зависимостей для разделения вещей.
Почему инъекция зависимости?
Цель Dependency Injection на самом деле не в тестируемости, а в том, чтобы взять тесно связанные приложения и максимально ослабить связь. (Что является желательным благодаря продукту, который делает ваш код намного проще для адаптации для правильного модульного тестирования)
Почему я должен беспокоиться о связи вообще?
Сцепление или жесткая зависимость могут быть очень опасными. (Особенно в скомпилированных языках). В этих случаях у вас может быть библиотека, dll и т. Д., Которая используется очень редко и имеет проблему, которая фактически переводит все приложение в автономный режим. (Все ваше приложение умирает, потому что неважная часть имеет проблему ... это плохо ... ДЕЙСТВИТЕЛЬНО плохо) Теперь, когда вы разделяете вещи, вы действительно можете настроить свое приложение так, чтобы оно могло работать, даже если эта DLL или библиотека полностью отсутствует! Уверен, что один кусок, которому нужна эта библиотека или DLL, не будет работать, но остальные приложения будут довольны, насколько это возможно.
Зачем мне нужен Dependency Injection для правильного тестирования
На самом деле вы просто хотите получить слабосвязанный код, а Dependency Injection просто позволяет это сделать. Вы можете свободно соединять вещи без IoC, но обычно это более трудоемкий и менее адаптируемый (я уверен, что у кого-то есть исключение)
В случае, который вы дали, я думаю, что было бы намного проще просто установить внедрение зависимостей, поэтому я могу смоделировать код, который меня не интересует считать в рамках этого теста. Просто сказать вам метод «Эй , я знаю , что я сказал вам позвонить в хранилище, но вместо этого вот те данные, которые „должны“возвратные подмигивает » сейчас, потому что эти данные никогда не меняются, вы знаете, что вы тестируете только часть, которая использует эти данные, а не фактический поиск данных.
В основном, при тестировании вам нужны интеграционные (функциональные) тесты, которые тестируют часть функциональности от начала до конца, и полное модульное тестирование, которое тестирует каждый фрагмент кода (обычно на уровне метода или функции) независимо.
Идея состоит в том, что вы хотите убедиться, что вся функциональность работает, если не хотите знать точную часть кода, которая не работает.
Это МОЖЕТ быть сделано без внедрения зависимостей, но обычно по мере роста вашего проекта становится все более и более громоздким делать это без внедрения зависимостей. (ВСЕГДА предполагайте, что ваш проект будет развиваться! Лучше иметь ненужную практику полезных навыков, чем находить проект, который быстро набирает обороты и требует серьезного рефакторинга и реинжиниринга после того, как что-то уже взлетело.)
источник
Как я уже упоминал в другом ответе , проблема заключается в том, что вы хотите, чтобы класс
A
зависел от какого-то классаB
без жесткого программирования, какой классB
используется в исходном коде A. Это невозможно в Java и C #, потому что единственный способ импортировать класс это ссылаться на него глобально уникальным именем.Используя интерфейс, вы можете обойти жестко запрограммированную зависимость класса, но вам все еще нужно получить экземпляр интерфейса, и вы не можете вызывать конструкторы, или вы вернулись на круги своя. Итак, теперь код в противном случае это может привести к возникновению его зависимостей, и это перекладывает эту ответственность на кого-то другого. И его зависимости делают то же самое. Так что теперь каждый раз, когда вам нужен экземпляр класса, вы в конечном итоге создаете все дерево зависимостей вручную, тогда как в случае, когда класс A напрямую зависит от B, вы можете просто вызвать
new A()
этот вызов конструктораnew B()
и т. Д.Инфраструктура внедрения зависимостей пытается обойти это, позволяя вам указать сопоставления между классами и построить для вас дерево зависимостей. Подвох в том, что когда вы облажаете сопоставления, вы узнаете об этом во время выполнения, а не во время компиляции, как в языках, которые поддерживают модули сопоставления как первоклассную концепцию.
источник
Я думаю, что это большое недоразумение здесь.
Guice - это структура внедрения зависимостей . Это делает DI автоматически . Суть, которую они указали в приведенной вами выдержке, заключается в том, что Guice избавился от необходимости вручную создавать «тестируемый конструктор», который вы представили в своем примере. Это не имеет абсолютно никакого отношения к внедрению зависимости.
Этот конструктор:
уже использует внедрение зависимостей. Вы просто сказали, что использовать DI легко.
Проблема, которую решает Guice, состоит в том, что для использования этого конструктора у вас должен быть где-то код конструктора графа объектов , который вручную передает уже созданные объекты в качестве аргументов этого конструктора. Guice позволяет вам иметь единственное место, где вы можете настроить, какие классы реальной реализации соответствуют тем
CreditCardProcessor
иTransactionLog
интерфейсам. После этой конфигурации каждый раз, когда вы создаетеBillingService
с помощью Guice, эти классы автоматически передаются в конструктор.Это то, что делает структура внедрения зависимостей . Но сам конструктор, который вы представили, уже является реализацией принципа внедрения зависимостей . Контейнеры IoC и DI-фреймворки - это средства для автоматизации соответствующих принципов, но ничто не мешает вам делать все вручную, в этом весь смысл.
источник