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

118

Внедрение зависимостей (DI) является хорошо известным и модным паттерном. Большинство инженеров знают его преимущества, такие как:

  • Обеспечение изоляции в модульном тестировании возможно / просто
  • Явно определяющие зависимости класса
  • Содействие хорошему дизайну (например, принцип единой ответственности )
  • Быстрое включение реализаций (например, DbLoggerвместо ConsoleLogger)

Я считаю, что в отрасли существует единодушное мнение, что DI - хороший, полезный шаблон. Сейчас не так уж много критики. Недостатки, которые упоминаются в сообществе, обычно незначительны. Некоторые из них:

  • Увеличение количества классов
  • Создание ненужных интерфейсов

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

Вещи, которые я хотел бы спросить:

  • Должны ли мы использовать внедрение зависимостей, когда у нас есть только одна реализация?
  • Должны ли мы запретить создание новых объектов, кроме языковых / каркасных?
  • Является ли внедрение одной реализации плохой идеей (скажем, у нас есть только одна реализация, поэтому мы не хотим создавать «пустой» интерфейс), если мы не планируем модульное тестирование определенного класса?
Landeeyo
источник
33
Вы действительно спрашиваете об инъекции зависимостей как шаблоне или об использовании DI-фреймворков? Это действительно разные вещи, вы должны уточнить, какая часть проблемы вас интересует, или явно спросить об обоих.
Frax
10
@ Фрейкс о шаблоне, а не о фреймворках
Landeeyo
10
Вы путаете инверсию зависимости с внедрением зависимости. Первый - это принцип дизайна. Последний представляет собой методику (обычно реализуемую с помощью существующего инструмента) для построения иерархий объектов.
jpmc26
3
Я часто пишу тесты, используя реальную базу данных и никаких фиктивных объектов вообще. Работает очень хорошо во многих случаях. И тогда вам больше не нужны интерфейсы. Если у вас есть UserServiceэтот класс просто держатель для логики. Он вставляет соединение с базой данных и запускает тесты внутри транзакции, которая откатывается. Многие назвали бы это плохой практикой, но я обнаружил, что это работает очень хорошо. Не нужно искажать код только для тестирования, и вы получаете возможность обнаружения ошибок в интеграционных тестах.
USR
3
ДИ почти всегда хорош. Плохо то, что многие люди думают, что знают DI, но все, что они знают, это как использовать какую-то странную среду, даже не будучи уверенным в том, что они делают. DI в настоящее время очень страдает от программирования Cargo Cult.
Т. Сар

Ответы:

160

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

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

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

Из-за того, что более 2 человек имеют путаницу по поводу термина «родитель» и «ребенок» в контексте внедрения зависимости:

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

Внедрение зависимостей - это шаблон для композиции объекта .

Почему интерфейсы?

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

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

Почему рамки?

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

  • Автосвязь зависимостей к компонентам
  • Настройка компонентов с какими-то настройками
  • Автоматизация кода котельной плиты, чтобы вам не приходилось видеть его написанным в нескольких местах.

У них также есть следующие недостатки:

  • Родительский объект - это «контейнер», и ничего в вашем коде
  • Это усложняет тестирование, если вы не можете предоставить зависимости непосредственно в тестовом коде
  • Это может замедлить инициализацию, поскольку она разрешает все зависимости, используя отражение и многие другие приемы.
  • Отладка во время выполнения может быть более сложной, особенно если контейнер внедряет прокси между интерфейсом и фактическим компонентом, который реализует интерфейс (на ум приходит аспектно-ориентированное программирование, встроенное в Spring). Контейнер - это черный ящик, и он не всегда построен с какой-либо концепцией, облегчающей процесс отладки.

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

Как насчет [случайной статьи в Интернете]?

Что насчет этого? Много раз люди могут переусердствовать, добавлять кучу ограничений и ругать вас, если вы не делаете «единственно верный путь». Там нет одного верного пути. Посмотрите, сможете ли вы извлечь из статьи что-нибудь полезное и игнорировать то, с чем вы не согласны.

Короче, подумай сам и попробуй.

Работа со "старыми головами"

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

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

Берин Лорич
источник
6
@CarlLeth, я работал с несколькими фреймворками от Spring до .net вариантов. Spring позволит вам внедрять реализации прямо в приватные поля, используя некоторую чёрную магию отражения / загрузчика классов. Единственный способ проверить компоненты, построенные таким образом, это использовать контейнер. В Spring есть бегуны JUnit для настройки тестовой среды, но это сложнее, чем настраивать себя самостоятельно. Так что да, я просто привел практический пример.
Берин Лорич
17
Есть еще один недостаток, который я считаю препятствием для DI из-за фреймворков, когда я ношу шляпу устранения неполадок / сопровождающего: жуткое действие на расстоянии, которое они обеспечивают, затрудняет отладку в автономном режиме. В худшем случае мне нужно запустить код, чтобы увидеть, как зависимости инициализируются и передаются. Вы упоминаете об этом в контексте «тестирования», но на самом деле гораздо хуже, если вы только начинаете смотреть на исходный код, никогда возражайте, пытаясь заставить его работать (что может потребовать массу настроек). Препятствовать моей способности сказать, что делает код, просто взглянув на него, - это плохо.
Йерун Мостерт
1
Интерфейсы - это не контракты, а просто API. Контракты подразумевают семантику. Этот ответ использует терминологию, специфичную для языка, и соглашения, специфичные для Java / C #.
Фрэнк Хайлеман,
2
@BerinLoritsch Суть вашего собственного ответа в том, что принцип DI! = Любая заданная структура DI. Тот факт, что Spring может делать ужасные, непростительные вещи, является недостатком Spring, а не DI-фреймворков в целом. Хорошая структура DI поможет вам следовать принципу DI без неприятных уловок.
Карл Лет
1
@CarlLeth: все DI-фреймворки предназначены для удаления или автоматизации некоторых вещей, которые программист не хочет описывать, они просто различаются по способу. Насколько мне известно, все они лишают возможности узнать, как (или если ) взаимодействуют классы А и В, просто взглянув на А и В - по крайней мере, вам также необходимо взглянуть на настройку / настройку DI. конвенций. Не проблема для программиста (на самом деле это именно то, что они хотят), а потенциальная проблема для сопровождающего / отладчика (возможно, того же программиста, позже). Это компромисс, который вы делаете, даже если ваша структура DI "идеальна".
Йерун Мостерт
88

Внедрение зависимостей, как и большинство моделей, является решением проблем . Поэтому начните с вопроса, есть ли у вас вообще проблема? Если нет, то с помощью шаблона , скорее всего, сделает код хуже .

Сначала подумайте, можете ли вы уменьшить или устранить зависимости. При прочих равных условиях мы хотим, чтобы каждый компонент в системе имел как можно меньше зависимостей. И если зависимости исчезли, вопрос о введении или нет становится спорным!

Рассмотрим модуль, который загружает некоторые данные из внешней службы, анализирует их, выполняет сложный анализ и записывает результаты в файл.

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

Но гораздо лучшим решением будет просто отделить анализ от ввода / вывода. Если анализ извлекается в модуль без побочных эффектов, его будет гораздо проще протестировать. Обратите внимание, что издевательство - это запах кода - его не всегда можно избежать, но в целом лучше тестировать, не полагаясь на насмешку. Таким образом, устраняя зависимости, вы избегаете проблем, которые DI должен облегчить. Обратите внимание, что такой дизайн также лучше придерживается SRP.

Я хочу подчеркнуть, что DI не обязательно способствует SRP или другим хорошим принципам проектирования, таким как разделение проблем, высокая когезия / низкая связь и так далее. С таким же успехом это может иметь противоположный эффект. Рассмотрим класс A, который использует другой класс B. B используется только A и поэтому полностью инкапсулирован и может рассматриваться как деталь реализации. Если вы измените это, чтобы внедрить B в конструктор A, то вы раскрыли детали этой реализации, и теперь знания об этой зависимости и о том, как инициализировать B, время жизни B и т. Д. Должны существовать в каком-то другом месте в системе отдельно. От А. Таким образом, у вас общая архитектура хуже с утечками.

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

Проблема в том, что шаблоны и архитектуры сами по себе становятся целями, а не инструментами. Просто спрашиваю "Должны ли мы использовать DI?" вроде ставит телегу перед лошадью. Вы должны спросить: «Есть ли у нас проблемы?» и "Как лучше всего решить эту проблему?"

Часть вашего вопроса сводится к следующему: «Должны ли мы создавать лишние интерфейсы для удовлетворения требований шаблона?» Вы, наверное, уже поняли ответ на этот вопрос - абсолютно нет ! Любой, кто говорит вам иначе, пытается продать вам что-то - скорее всего, дорогие консультационные часы. Интерфейс имеет значение, только если он представляет абстракцию. Интерфейс, который просто имитирует поверхность отдельного класса, называется «интерфейсом заголовка», и это известный антипаттерн.

JacquesB
источник
15
Я не мог согласиться больше! Также обратите внимание, что ради насмешек это означает, что мы на самом деле не тестируем реальные реализации. Если Aиспользуется Bв производстве, но был протестирован только на соответствие MockB, наши тесты не говорят нам, будет ли он работать в производстве. Когда чистые (без побочных эффектов) компоненты доменной модели внедряют и высмеивают друг друга, результатом является огромная трата времени каждого, раздутая и хрупкая кодовая база и низкая уверенность в получающейся системе. Издевайтесь над границами системы, а не между произвольными частями одной и той же системы.
Warbo
17
@CarlLeth Почему, по вашему мнению, DI делает код «тестируемым и поддерживаемым», а кода без него меньше? JacquesB прав, что побочные эффекты - это то, что вредит тестируемости / ремонтопригодности. Если у кода нет побочных эффектов, нам все равно, что / где / когда / как он вызывает другой код; мы можем сделать это простым и прямым. Если у кода есть побочные эффекты, мы должны заботиться. DI может извлекать побочные эффекты из функций и помещать их в параметры, делая эти функции более тестируемыми, а программу - более сложной. Иногда это неизбежно (например, доступ к БД). Если у кода нет побочных эффектов, DI - просто бесполезная сложность.
Warbo
13
@CarlLeth: DI - это одно из решений проблемы создания кода, который можно тестировать изолированно, если в нем есть зависимости, которые запрещают его. Но это не уменьшает общую сложность и не делает код более читабельным, что означает, что он не обязательно увеличивает удобство сопровождения. Однако, если все эти зависимости могут быть устранены путем лучшего разделения интересов, это совершенно «сводит на нет» преимущества DI, поскольку сводит на нет необходимость в DI. Часто это лучшее решение, чтобы сделать код более тестируемым и обслуживаемым одновременно.
Док Браун
5
@Warbo Это был оригинальный и до сих пор, вероятно, единственный действительный способ использования насмешек. Даже на системных границах это редко требуется. Люди действительно тратят много времени на создание и обновление практически бесполезных тестов.
Фрэнк Хайлман,
6
@CarlLeth: хорошо, теперь я вижу, откуда исходит недоразумение. Вы говорите об инверсии зависимости . Но, вопрос, этот ответ и мои комментарии о DI = depency инъекции .
Док Браун
36

По моему опыту, у внедрения зависимости есть ряд недостатков.

Во-первых, использование DI не настолько упрощает автоматизированное тестирование, сколько рекламируется. Модульное тестирование класса с фиктивной реализацией интерфейса позволяет проверить, как этот класс будет взаимодействовать с интерфейсом. Таким образом, он позволяет вам тестировать модули, как тестируемый класс использует контракт, предоставленный интерфейсом. Однако это обеспечивает гораздо большую уверенность в том, что ввод тестируемого класса в интерфейс соответствует ожиданиям. Это дает довольно слабую уверенность в том, что тестируемый класс реагирует так, как ожидается, на вывод из интерфейса, поскольку это почти универсальный фиктивный вывод, который сам по себе подвержен ошибкам, упрощениям и так далее. Короче говоря, это НЕ позволяет вам проверить, что класс будет вести себя так, как ожидается с реальной реализацией интерфейса.

Во-вторых, DI значительно усложняет навигацию по коду. При попытке перейти к определению классов, используемых в качестве входных данных для функций, интерфейс может быть чем угодно, от незначительного раздражения (например, при наличии единственной реализации) до большого расхода времени (например, когда используется слишком общий интерфейс, такой как IDisposable) при попытке найти фактическую реализацию используется. Это может превратить простое упражнение типа «Мне нужно исправить исключение нулевой ссылки в коде, которое происходит сразу после того, как напечатан этот оператор ведения журнала», в однодневное усилие.

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

Эрик
источник
Я также хотел бы добавить, что чем больше вы вводите, тем больше будет время запуска. Большинство платформ DI создают все инъецируемые одноэлементные экземпляры во время запуска, независимо от того, где они используются.
Родни П. Барбати
7
Если вы хотите протестировать класс с реальной реализацией (не имитацией), вы можете написать функциональные тесты - тесты, похожие на модульные тесты, но без использования имитаций.
BЈовић
2
Я думаю, что ваш второй абзац должен быть более нюансированным: сам по себе DI не усложняет навигацию по коду. В простейшем случае DI - это просто следование SOLID. Что увеличивает сложность, так это использование ненужных косвенных и DI-структур . Кроме этого, этот ответ ударяет гвоздь по голове.
Конрад Рудольф
4
Внедрение зависимостей, за исключением случаев, когда это действительно необходимо, также является предупреждением о том, что другой лишний код может присутствовать в большом количестве. Руководители часто удивляются, узнав, что разработчики добавляют сложность ради сложности.
Фрэнк Хилман
1
+1. Это реальный ответ на вопрос, который был задан, и должен быть принятым ответом.
Мейсон Уилер
13

Я следовал совету Марка Симанна из «Инъекции зависимостей в .NET» - чтобы догадаться.

DI следует использовать, когда у вас есть «изменчивая зависимость», например, есть разумная вероятность, что она может измениться.

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

обкрадывать
источник
5
Обратите внимание, что он также дает различные советы для ОО и функциональных языков. Blog.ploeh.dk/2017/01/27/…
jk.
1
Это хороший момент. Если мы создаем интерфейсы для каждой зависимости по умолчанию, то это как-то против YAGNI.
Landeeyo
4
Можете ли вы дать ссылку на «Внедрение зависимостей в .NET» ?
Питер Мортенсен
1
Если вы проводите модульное тестирование, то весьма вероятно, что ваша зависимость является изменчивой.
Хакес
11
Хорошо, что разработчики всегда могут предсказать будущее с идеальной точностью.
Фрэнк Хайлеман,
5

Моя самая большая любимая мозоль о DI уже упоминалась в нескольких ответах, но я немного подробнее остановлюсь здесь. DI (как это обычно делается сегодня, с контейнерами и т. Д.) Действительно, действительно ухудшает читабельность кода. И читабельность кода, возможно, является причиной большинства современных инноваций в программировании. Как кто-то сказал - писать код легко. Читать код сложно. Но это также чрезвычайно важно, если только вы не пишете какую-то крошечную утилиту для однократной записи.

Проблема с DI в этом отношении в том, что он непрозрачный. Контейнер представляет собой черный ящик. Объекты просто откуда-то появляются, и вы понятия не имеете - кто их создал и когда? Что было передано конструктору? С кем я делюсь этим экземпляром? Кто знает...

А когда вы работаете в основном с интерфейсами, все возможности вашей среды IDE "идут к определению" оказываются в дыму. Очень трудно отследить поток программы, не запуская ее и просто шагая к ней, чтобы увидеть, КАКОЕ внедрение интерфейса было использовано в ЭТОМ конкретном месте. И иногда возникает какое-то техническое препятствие, которое не дает вам пройти. И даже если вы можете, если это связано с прохождением витой кишки контейнера DI, все это быстро превращается в разочарование.

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

Vilx-
источник
3

Быстрое включение реализации (например, DbLogger вместо ConsoleLogger)

Хотя DI в целом, безусловно, хорошая вещь, я бы посоветовал не использовать его вслепую для всего. Например, я никогда не впрыскиваю регистраторы. Одно из преимуществ DI - сделать зависимости явными и понятными. Нет никакого смысла в перечислении ILoggerкак зависимости почти каждого класса - это просто беспорядок. Регистратор несет ответственность за обеспечение необходимой гибкости. Все мои регистраторы являются статическими конечными участниками, я могу рассмотреть возможность внедрения регистратора, когда мне нужен нестатический.

Увеличение количества классов

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

Создание ненужных интерфейсов

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

Должны ли мы использовать внедрение зависимостей, когда у нас есть только одна реализация?

Да, но избегайте любых дополнительных шаблонов .

Должны ли мы запретить создание новых объектов, кроме языковых / каркасных?

Нет. Будет много классов значений (неизменяемых) и данных (изменяемых), где экземпляры просто создаются и передаются, и где нет смысла их вводить - так как они никогда не сохраняются в другом объекте (или только в другом). такие объекты).

Для них вам может потребоваться ввести фабрику вместо этого, но в большинстве случаев это не имеет смысла (представьте, например, что @Value class NamedUrl {private final String name; private final URL url;}вам действительно не нужна фабрика здесь и нечего вводить).

Является ли внедрение одной реализации плохой идеей (скажем, у нас есть только одна реализация, поэтому мы не хотим создавать «пустой» интерфейс), если мы не планируем модульное тестирование определенного класса?

ИМХО, это нормально, пока не вызывает раздувания кода. Внедрите зависимость, но не создавайте интерфейс (и без сумасшедшего конфигурационного XML!), Как вы можете сделать это позже без каких-либо хлопот.

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


Другим недостатком большинства платформ DI являются накладные расходы времени выполнения. Это можно перенести во время компиляции (для Java есть Dagger , понятия не имею о других языках).

Еще один недостаток - магия, происходящая повсюду, которую можно отключить (например, я отключил создание прокси при использовании Guice).

maaartinus
источник
-4

Я должен сказать, что, по моему мнению, само понятие внедрения зависимостей переоценено.

DI является современным эквивалентом глобальных ценностей. Внедряемые вами вещи - это глобальные синглтоны и объекты чистого кода, иначе вы не сможете их внедрить. В большинстве случаев использование DI навязано вам для использования данной библиотеки (JPA, Spring Data и т. Д.). По большей части, DI обеспечивает идеальную среду для выращивания и выращивания спагетти.

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

Затем вы создаете экземпляр класса Test и тестируете все его методы. Это не будет понятно некоторым из вас - тестируемые вами методы относятся к тестируемому классу. И все эти тесты методов происходят в одном файле класса - классе модульного тестирования, связанном с тестируемым классом. Здесь нет никаких накладных расходов - так работает модульное тестирование.

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

class ClassUnderTest {

   protected final ADependency;
   protected final AnotherDependency;

   // Call from a factory or use an initializer 
   public void initializeDependencies() {
      aDependency = new ADependency();
      anotherDependency = new AnotherDependency();
   }
}

class TestClassUnderTest extends ClassUnderTest {

    @Override
    public void initializeDependencies() {
      aDependency = new MockitoObject();
      anotherDependency = new MockitoObject();
    }

    // Unit tests go here...
    // Unit tests call base class methods
}

Результат в точности эквивалентен использованию DI - то есть ClassUnderTest настроен для тестирования.

Разница лишь в том, что этот код очень лаконичен, полностью инкапсулирован, проще в кодировании, легче для понимания, быстрее, использует меньше памяти, не требует альтернативной конфигурации, не требует каких-либо структур, никогда не будет причиной появления 4-х страниц. (WTF!) Трассировка стека, которая включает в себя именно те классы ZERO (0), которые вы написали, и совершенно очевидна для любого, имеющего даже малейшее знание ОО, от новичка до Гуру (вы думаете, но ошибаетесь).

При этом, конечно, мы не можем использовать это - это слишком очевидно и недостаточно модно.

В конце концов, тем не менее, меня больше всего беспокоит DI, потому что проекты, которые я видел, с треском провалились, все они были массивными базами кода, где DI был связующим звеном. DI - это не архитектура, это действительно актуально только в нескольких ситуациях, большинство из которых навязывается вам для использования другой библиотеки (JPA, Spring Data и т. Д.). По большей части, в хорошо разработанной кодовой базе большинство случаев использования DI будет происходить на уровне ниже, чем ваши ежедневные действия по разработке.

Родни П. Барбати
источник
6
Вы описали не эквивалент, а противоположность внедрения зависимости. В вашей модели каждый объект должен знать конкретную реализацию всех его зависимостей, но используя DI, который становится обязанностью «основного» компонента - склеивать соответствующие реализации вместе. Имейте в виду, что DI идет рука об руку с другим DI - инверсией зависимостей, где вы не хотите, чтобы компоненты высокого уровня имели жесткие зависимости от компонентов низкого уровня.
Логан Пикап
1
это удобно, когда у вас есть только один уровень наследования классов и один уровень зависимостей. Конечно, он превратится в ад на земле, как он расширяется?
Эван
4
если вы используете интерфейсы и перемещаете initializeDependencies () в конструктор, он такой же, но более аккуратный. Следующий шаг, добавление параметров конструкции, означает, что вы можете отказаться от всех ваших TestClasses.
Эван
5
В этом так много неправильного. Как уже говорили другие, ваш «DI-эквивалентный» пример вовсе не является инъекцией зависимостей, это антитеза, он демонстрирует полное непонимание концепции, а также создает другие потенциальные подводные камни: частично инициализированные объекты являются запахом кода. Как Ewan предлагает переместить инициализацию в конструктор и передать их через параметры конструктора. Тогда у вас есть DI ...
Mr.Mindor
3
Добавление к @ Mr.Mindor: есть более общая анти-шаблонная «последовательная связь», которая не относится только к инициализации. Если методы объекта (или, в более общем смысле, вызовы API) должны выполняться в определенном порядке, например, barмогут вызываться только после foo, то это плохой API. Он заявляет о предоставлении функции ( bar), но на самом деле мы не можем его использовать (поскольку, fooвозможно, его не вызывали). Если вы хотите придерживаться своего initializeDependencies(анти?) Паттерна, вы должны по крайней мере сделать его закрытым / защищенным и автоматически вызывать его из конструктора, чтобы API был искренним.
Warbo
-6

На самом деле ваш вопрос сводится к "Является ли юнит-тестирование плохим?"

99% ваших альтернативных классов для инъекций будут mocks, которые позволяют юнит-тестирование.

Если вы проводите модульное тестирование без DI, у вас возникает проблема, как заставить классы использовать поддельные данные или поддельные службы. Скажем «часть логики», так как вы не можете разделить ее на сервисы.

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

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

Ewan
источник
13
Я не согласен с вашим утверждением о том, что DI касается модульного тестирования. Облегчение модульного тестирования - это только одно из преимуществ DI, и, возможно, не самое важное.
Роберт Харви
5
Я не согласен с вашей предпосылкой, что юнит-тестирование и DI так близки. Используя макет / заглушку, мы заставляем набор тестов лгать нам немного больше: тестируемая система идет дальше от реальной системы. Это объективно плохо. Иногда это перевешивает положительные стороны: поддельные вызовы FS не требуют очистки; ложные HTTP-запросы быстрые, детерминированные и работают в автономном режиме; и т. д. Напротив, каждый раз, когда мы используем жестко закодированный newвнутри метода, мы знаем, что во время тестов выполнялся один и тот же код, работающий в рабочей среде.
Warbo
8
Нет, это не «плохо ли модульное тестирование?», Это «издевательство (а) действительно необходимо и (б) стоит увеличения сложности?» Это совсем другой вопрос. Модульное тестирование не плохо ( в буквальном смысле никто не спорит , это), и это, безусловно , стоит в целом. Но не все модульное тестирование требует насмешек, и имитация требует значительных затрат, поэтому, по крайней мере, ее следует использовать разумно.
Конрад Рудольф
6
@Ewan После вашего последнего комментария я не думаю, что мы согласны. Я говорю, что большинству модульных тестов не нужны DI [фреймворки] , потому что большинству модульных тестов не требуется макет. Фактически, я бы даже использовал это как эвристику для качества кода: если большая часть вашего кода не может быть модульно протестирована без объектов DI / mock, то вы написали плохой код, который был слишком правильно связан. Большая часть кода должна быть сильно отделена, иметь единую ответственность и быть универсальной, а также быть тривиально тестируемой в изоляции.
Конрад Рудольф
5
@Ewan Ваша ссылка дает хорошее определение модульного тестирования. По этому определению мой Orderпример - модульное тестирование: это тестирование метода ( totalметода Order). Вы жалуетесь, что это код вызова из 2 классов, мой ответ - ну и что ? Мы не тестируем «2 класса одновременно», мы тестируем totalметод. Нас не должно волновать, как метод выполняет свою работу: это детали реализации; их тестирование вызывает хрупкость, тесную связь и т. д. Мы заботимся только о поведении метода (возвращаемое значение и побочные эффекты), а не о классах / модулях / регистрах ЦП / и т. д. это используется в процессе.
Warbo