Каковы недостатки использования инъекций зависимостей?
Я пытаюсь представить DI как образец здесь, на работе, и один из наших ведущих разработчиков хотел бы знать: что - если есть - downsides для использования шаблона Injection Dependency?
Примечание. Я ищу здесь, если возможно, исчерпывающий список, а не субъективное обсуждение темы.
Уточнение. Я говорю об шаблоне Injection Dependency (см. в этой статье от Мартина Фаулера), а не определенную структуру, будь то на основе XML (например, Spring) или на основе кода (например, Guice), или "самокатализированный".
Изменить. Более подробно обсуждается/разглашается/обсуждается /r/programming здесь.
Ответы
Ответ 1
Несколько пунктов:
- DI увеличивает сложность, как правило, путем увеличения количества классов, поскольку обязанности разделяются больше, что не всегда выгодно.
- Ваш код будет (несколько) связан с используемой инфраструктурой инъекций зависимостей (или, в более общем смысле, как вы решили реализовать шаблон DI)
- Контейнеры DI или подходы, которые выполняют разрешение типа, как правило, подвергаются незначительному штрафу за выполнение (очень незначительно, но там).
Как правило, преимущество развязки упрощает чтение и понимание каждой задачи, но увеличивает сложность организации более сложных задач.
Ответ 2
Та же самая основная проблема, которую вы часто получаете с объектно-ориентированным программированием, правилами стиля и почти всем остальным. Возможно - очень часто, на самом деле, делать слишком много абстракции и добавлять слишком много косвенных указаний и обычно применять хорошие методы чрезмерно и в неправильных местах.
Каждый шаблон или другая конструкция, которую вы применяете, приносит сложность. Абстракция и косвенная информация о рассеивании вокруг, иногда перемещая нерелевантные детали в сторону, но в равной степени иногда затрудняют понимание того, что происходит. Каждое применяемое вами правило привносит негибкость, исключая варианты, которые могут быть наилучшим подходом.
Цель состоит в том, чтобы написать код, который выполняет задание, и является надежным, читаемым и поддерживаемым. Вы разработчик программного обеспечения - не строитель башни из слоновой кости.
Релевантные ссылки
http://thedailywtf.com/Articles/The_Inner-Platform_Effect.aspx
http://www.joelonsoftware.com/articles/fog0000000018.html
Вероятно, простейшая форма инъекции зависимостей (не смеяться) является параметром. Зависимый код зависит от данных, и эти данные вводятся посредством передачи параметра.
Да, это глупо и не относится к объектно-ориентированной точке инъекции зависимостей, но функциональный программист скажет вам, что (если у вас есть функции первого класса), это единственный вид инъекции зависимостей, в которой вы нуждаетесь. Дело здесь в том, чтобы принять тривиальный пример и показать потенциальные проблемы.
Давайте возьмем эту простую традиционную функцию - синтаксис С++ здесь не является существенным, но я должен как-то его записать...
void Say_Hello_World ()
{
std::cout << "Hello World" << std::endl;
}
У меня есть зависимость, которую я хочу извлечь и вставить - текст "Hello World". Легко...
void Say_Something (const char *p_text)
{
std::cout << p_text << std::endl;
}
Как это более негибко, чем оригинал? Что ж, если я решаю, что вывод должен быть unicode. Я, вероятно, хочу переключиться с std:: cout на std:: wcout. Но это значит, что мои строки должны быть wchar_t, а не char. Любой из вызывающих абонентов должен быть изменен или (более разумно), старая реализация заменяется адаптером, который переводит строку и вызывает новую реализацию.
Эта работа по техническому обслуживанию там не понадобилась, если бы мы сохранили оригинал.
И если это кажется тривиальным, взгляните на эту реальную функцию из Win32 API...
http://msdn.microsoft.com/en-us/library/ms632680%28v=vs.85%29.aspx
Это 12 "зависимостей". Например, если разрешения экрана действительно огромны, возможно, нам понадобятся 64-битные координатные значения - и еще одна версия CreateWindowEx. И да, уже существует более старая версия, которая предположительно попадает в новую версию за кулисами...
http://msdn.microsoft.com/en-us/library/ms632679%28v=vs.85%29.aspx
Эти "зависимости" не просто проблема для первоначального разработчика - каждый, кто использует этот интерфейс, должен искать, что такое зависимости, как они указаны, и что они означают, и выработать, что делать для их выражение. Здесь слова "разумные дефолты" могут сделать жизнь намного проще.
Объектно-ориентированная инъекция зависимостей принципиально не отличается. Написание класса является накладными расходами, как в тексте исходного кода, так и во время разработки, и если этот класс написан для предоставления зависимостей в соответствии с некоторыми зависимыми объектами, тогда зависимый объект блокируется для поддержки этого интерфейса, даже если есть необходимость для замены реализации этого объекта.
Ничего из этого не следует читать, утверждая, что инъекция зависимостей плоха - далеко от нее. Но любая хорошая техника может применяться чрезмерно и в неправильном месте. Так же, как не каждая строка должна быть извлечена и преобразована в параметр, не все низкоуровневые поведения должны быть извлечены из объектов высокого уровня и превращены в инъекционную зависимость.
Ответ 3
Здесь моя собственная первоначальная реакция: в основном те же самые недостатки любого шаблона.
- требуется время, чтобы узнать
- Если это неправильно понято, это может привести к большему вреду, чем к лучшему.
- если принять его до крайности, это может быть больше работы, чем оправдывает выгоду.
Ответ 4
Самый большой "недостаток" для Inversion of Control (не совсем DI, но достаточно близко) состоит в том, что он стремится удалить одну точку, чтобы посмотреть обзор алгоритма. Это в основном то, что происходит, когда вы имеете развязанный код, хотя - способность смотреть в одном месте - это артефакт жесткой связи.
Ответ 5
Я не думаю, что такой список существует, однако попробуйте прочитать эти статьи:
Ответ 6
Я использую Guice (Java DI framework) в течение последних 6 месяцев. Хотя в целом я думаю, что это здорово (особенно с точки зрения тестирования), есть некоторые недостатки. Наиболее заметно:
- Код может усложниться. Инъекционная инъекция может использоваться в очень... творческих... способах. Например, я просто наткнулся на некоторый код, который использовал пользовательскую аннотацию для ввода определенных IOStreams (например: @Server1Stream, @Server2Stream). Хотя это действительно работает, и я признаю, что имеет определенную элегантность, он делает понимание инъекций Guice необходимым условием для понимания кода.
- Высшая кривая обучения при изучении проекта. Это связано с точкой 1. Чтобы понять, как работает проект, использующий инъекцию зависимостей, вам нужно понять как шаблон инъекции зависимостей, так и конкретную структуру, Когда я начал с моей текущей работы, я потратил немало смущенных часов на то, что Гуис делал за кулисами.
- Конструкторы становятся большими. Хотя это может быть в значительной степени разрешено с помощью конструктора по умолчанию или factory.
- Ошибки могут быть запутаны. Самый последний пример этого - столкновение с двумя именами флагов. Гуис молча проглотил ошибку, и один из моих флагов не был инициализирован.
- Ошибки приводятся во время выполнения. Если вы неправильно настроили свой модуль Guice (круговая ссылка, неверная привязка,...), большинство ошибок не обнаруживаются во время компиляции. Вместо этого ошибки отображаются при запуске программы.
Теперь я жаловался. Позвольте мне сказать, что я продолжу (охотно) использовать Guice в своем текущем проекте и, скорее всего, буду дальше. Инъекция - отличная и невероятно мощная модель. Но это определенно может сбивать с толку, и вы почти наверняка потратите некоторое время на проклятие любой зависимости, которую вы выбираете.
Кроме того, я согласен с другими плакатами в том, что инъекция зависимостей может быть чрезмерной.
Ответ 7
Код без какого-либо DI запускает известный риск запутаться в код спагетти - некоторые признаки того, что классы и методы слишком велики, слишком много делают и не может быть легко изменено, разбито, реорганизовано или протестировано.
Код с DI используется много, может быть код равиоли, где каждый маленький класс похож на отдельный самородок из равиоли - он делает одну маленькую вещь, а принцип единственной ответственности, что хорошо. Но, глядя на классы сами по себе, трудно понять, что делает система в целом, поскольку это зависит от того, как все эти многие мелкие детали подходят друг к другу, что трудно понять. Это похоже на большую кучу мелочей.
Избегая сложности спагетти больших битов связанного кода в большом классе, вы рискуете другой сложностью, где есть много простых маленьких классов, и взаимодействия между ними сложны.
Я не думаю, что это фатальный минус - DI все еще очень полезен. Вероятно, хороший стиль равиоли с небольшими классами, которые делают только одно. Даже в избытке, я не думаю, что это плохо, как код спагетти. Но осознание того, что его можно забрать слишком далеко, - это первый шаг к его устранению. Следуйте ссылкам, чтобы узнать, как их избежать.
Ответ 8
Если у вас есть доморощенные решения, зависимости прямо в вашем лице в конструкторе. Или, может быть, параметры метода, которые снова не так сложно обнаружить. Хотя управляемые зависимостями структуры, если они приняты до крайностей, могут появляться как магия.
Однако, слишком много зависимостей в слишком многих классах - это явный признак того, что вы являетесь классовой структурой. Таким образом, инъекция зависимостей (доморощенная или управляемая инфраструктурой) может помочь вывести вопиющие проблемы дизайна, которые в противном случае могли бы скрываться в темноте.
Чтобы проиллюстрировать второй пункт лучше, здесь выдержка из этой статьи ( оригинальный источник), который я искренне верю, является основной проблемой при построении любой системы, а не только компьютерных систем.
Предположим, вы хотите создать университетский городок. Вы должны делегировать часть дизайна студентам и преподавателям, иначе физическое здание не будет хорошо работать для физиков. Ни один архитектор не знает достаточно о том, что физики должны делать сами. Но вы не можете делегировать дизайн каждой комнаты своим жильцам, потому что тогда вы получите гигантскую кучу щебня.
Как вы можете распределить ответственность за дизайн на всех уровнях большой иерархии, сохраняя при этом согласованность и гармонию общего дизайна? Это проблема архитектурного дизайна, которую пытается решить Александр, но это также фундаментальная проблема развития компьютерных систем.
Решает ли DI эта проблема? Нет. Но это помогает вам четко видеть, если вы пытаетесь делегировать ответственность за проектирование каждой комнаты для своих пассажиров.
Ответ 9
Это больше подходит. Но один из недостатков инъекции зависимостей заключается в том, что он немного затрудняет разработку и использование кода для средств разработки.
В частности, если вы Control-Click/Command-Click на вызове метода в коде, он приведет вас к объявлению метода на интерфейсе вместо конкретной реализации.
Это скорее недостаток слабосвязанного кода (код, разработанный по интерфейсу), и применяется, даже если вы не используете инъекцию зависимостей (т.е. даже если вы просто используете фабрики). Но появление инъекции зависимостей - это то, что действительно поощряло слабосвязанный код к массам, поэтому я подумал, что упомянул об этом.
Кроме того, преимущества слабосвязанного кода намного перевешивают это, поэтому я называю это nitpick. Хотя я работал достаточно долго, чтобы знать, что это то, что вы можете получить, если попытаетесь ввести инъекцию зависимостей.
На самом деле, я бы рискнул предположить, что для каждого "недостатка", который вы можете найти для инъекции зависимостей, вы обнаружите много факторов, которые намного перевешивают его.
Ответ 10
Одна вещь, которая заставляет меня немного подшутить над DI, - это предположение, что все введенные объекты дешевы для создания экземпляра и не производят никаких побочных эффектов -OR- зависимость используется так часто, что она перевешивает любые связанные с этим затраты на создание экземпляра.
Если это может быть существенным, то когда зависимость не часто используется в классе потребления; например, IExceptionLogHandlerService
. Очевидно, что подобная услуга вызывается (надеюсь):) редко в классе - предположительно только на исключениях, которые нужно записывать; но канонический конструктор-инъекционный образец...
Public Class MyClass
Private ReadOnly mExLogHandlerService As IExceptionLogHandlerService
Public Sub New(exLogHandlerService As IExceptionLogHandlerService)
Me.mExLogHandlerService = exLogHandlerService
End Sub
...
End Class
... требует, чтобы был предоставлен "живой" экземпляр этой службы, проклятый стоимость/побочные эффекты, необходимые для его получения. Не то, чтобы это было возможно, но что, если построение этого экземпляра зависимостей включало в себя попадание службы/базы данных или поиск файлов конфигурации или блокировку ресурса до его удаления? Если бы эта служба была построена по мере необходимости, служба-местоположение или factory -генерированы (все проблемы имеют свои собственные), тогда вы будете брать затраты на строительство только при необходимости.
Теперь общепризнанным принципом разработки программного обеспечения является то, что построение объекта дешево и не создает побочных эффектов. И хотя это хорошее понятие, это не всегда так. Однако, используя типичную конструктор-инъекцию, в основном это требует. Если вы создаете реализацию зависимостей, вы должны сконструировать ее с учетом DI. Возможно, вы сделали бы объектное строительство более дорогостоящим, чтобы получить преимущества в другом месте, но если эта реализация будет введена, это, скорее всего, заставит вас пересмотреть этот дизайн.
Кстати, некоторые методы могут смягчить эту точную проблему, разрешив ленивую загрузку вложенных зависимостей, например. предоставляя экземпляр класса Lazy<IService>
в качестве зависимости. Это изменит конструкторы зависимых объектов и сделает еще более осведомленным о деталях реализации, таких как затраты на строительство объектов, что тоже нежелательно.
Ответ 11
Инъекция зависимостей на основе конструктора (без помощи магических "фреймворков" ) - это чистый и полезный способ структурирования OO-кода. В лучших кодовых базах, которые я видел, в течение многих лет, проведенных с другими бывшими коллегами Мартина Фаулера, я начал замечать, что большинство хороших классов, написанных таким образом, имеют один метод doSomething
.
Таким образом, основной недостаток заключается в том, что как только вы осознаете, что это всего лишь путаный длинный OO-способ написания закрытий в качестве классов, чтобы получить преимущества функционального программирования, ваша мотивация писать OO-код может быстро испариться.
Ответ 12
Иллюзия, что вы отделили свой код, просто внедряя инъекцию зависимостей, не вставляя его в действие. Я думаю, что самое опасное в DI.
Ответ 13
Я нахожу, что инсталляция конструктора может привести к большим уродливым конструкторам (и я использую его во всей своей кодовой базе), возможно, мои объекты слишком детализированы?). Кроме того, иногда с инжектором конструктора я получаю ужасные круговые зависимости (хотя это очень редко), поэтому вы можете оказаться в состоянии иметь какой-то готовый жизненный цикл с несколькими циклами инъекции зависимостей в более сложной системе.
Тем не менее, я предпочитаю инжектор конструктора за установку setter, потому что, как только мой объект сконструирован, я знаю, без сомнения, какое состояние оно находится, независимо от того, находится ли оно в среде unit test или загружено в некоторый контейнер IOC. Который, в круговом виде, говорит, что я считаю основным недостатком инъекции сеттера.
(в качестве опоры, я нахожу всю тему довольно "религиозной", но ваш пробег будет отличаться от уровня технического фанатизма в вашей команде разработчиков!)
Ответ 14
Если вы используете DI без контейнера IOC, самым большим недостатком является то, что вы быстро видите, сколько зависимостей имеет ваш код и насколько тесно связаны все на самом деле. ( "Но я думал, что это хороший дизайн!" ). Естественным шагом вперед является переход к контейнеру IOC, который может занять немного времени, чтобы учиться и реализовывать (не так плохо, как кривая обучения WPF, но она не бесплатна или). Последним недостатком является то, что некоторые разработчики начнут писать честные тесты модульности, и им потребуется время, чтобы понять это. Devs, которые раньше могли закричать что-то через полдня, вдруг проведут два дня, пытаясь понять, как издеваться над всеми их зависимостями.
Как и ответ Марка Seemann, суть в том, что вы тратите время на то, чтобы стать лучшим разработчиком, а не взламывать кусочки кода и бросать его в дверь/в производство. Что может быть у вашего бизнеса? Только вы можете ответить на это.
Ответ 15
DI - это метод или шаблон и не имеет отношения к какой-либо структуре. Вы можете связать свои зависимости вручную. DI помогает вам с SR (единоличная ответственность) и SoC (разделение проблем). DI ведет к лучшей конструкции. С моей точки зрения и опыта нет недостатков. Как и с любым другим шаблоном, вы можете ошибочно или неправильно использовать его (но то, что в случае с DI довольно сложно).
Если вы представляете DI как принцип для устаревшего приложения, используя фреймворк - самая большая ошибка, которую вы можете сделать, это неправильно использовать его как Service-Locater. Сама DI + сама по себе велика и просто улучшилась везде, где я ее видел! С организационной точки зрения, есть общие проблемы с каждым новым процессом, техникой, шаблоном,...:
- Вам нужно обучить команду.
- Вам необходимо изменить свое приложение (включая риски)
В общем, вы должны инвестировать время и деньги, кроме того, там нет недостатков, действительно!
Ответ 16
Чтение кода. Вы не сможете легко вычислить поток кода, поскольку зависимости скрыты в файлах XML.
Ответ 17
Две вещи:
- Они требуют дополнительной поддержки инструмента для проверки правильности конфигурации.
Например, IntelliJ (коммерческая версия) имеет поддержку проверки правильности конфигурации Spring и будет отмечать ошибки, такие как нарушения типа в конфигурации. Без такой поддержки инструмента вы не можете проверить правильность конфигурации перед запуском тестов.
Это одна из причин, по которой шаблон 'cake' (как известно сообществу Scala) является хорошей идеей: проводка между компонентами может быть проверена с помощью проверки типа. У вас нет этой выгоды с аннотациями или XML.
- Это делает глобальный статический анализ программ очень сложным.
Рамки, такие как Spring или Guice, затрудняют точное определение того, как будет выглядеть граф объектов, созданный контейнером. Хотя они создают граф объектов при запуске контейнера, они не предоставляют полезные API, которые описывают граф объекта, который/будет/должен быть создан.
Ответ 18
Похоже, что предполагаемые преимущества статически типизированного языка значительно уменьшаются, когда вы постоянно используете методы для работы по статической типизации. Один большой магазин Java, на котором я только что взял интервью, собирал свои зависимости от сборки с помощью статического анализа кода... для эффективного анализа всех файлов Spring.
Ответ 19
Это может увеличить время запуска приложения, потому что контейнер IoC должен правильно разрешать зависимости, и иногда требуется выполнить несколько итераций.