Я пытаюсь представить DI как образец здесь на работе, и один из наших ведущих разработчиков хотел бы знать: Каковы - если таковые имеются - недостатки в использовании шаблона внедрения зависимостей?
Заметьте, я ищу здесь - если возможно - исчерпывающий список, а не субъективное обсуждение по теме.
Пояснение : я говорю о паттерне внедрения зависимостей (см. Эту статью Мартина Фаулера), а не о конкретной платформе, будь то на основе XML (например, Spring) или на основе кода (например, Guice), или «самообращаемая» ,
Изменить : Некоторые большие дальнейшие обсуждения / разглагольствования / дебаты продолжаются / г / программирование здесь.
Ответы:
Пара моментов:
Как правило, выгода от разделения делает каждую задачу проще для чтения и понимания, но увеличивает сложность организации более сложных задач.
источник
Та же самая основная проблема, с которой вы часто сталкиваетесь в объектно-ориентированном программировании, правилах стиля и почти во всем остальном. Возможно - на самом деле очень распространено - делать слишком много абстракций и добавлять слишком много косвенных указаний, и, как правило, применять чрезмерно хорошие методы в неправильных местах.
Каждый шаблон или другая конструкция, которую вы применяете, приносит сложность. Абстракция и косвенность рассеивают информацию, иногда удаляя ненужные детали с пути, но в равной степени затрудняя точное понимание того, что происходит. Каждое правило, которое вы применяете, приносит негибкость, исключая варианты, которые могут быть лучшим подходом.
Суть в том, чтобы написать код, который выполняет свою работу и является надежным, читаемым и обслуживаемым. Вы разработчик программного обеспечения, а не строитель башни из слоновой кости.
Соответствующие ссылки
http://thedailywtf.com/Articles/The_Inner-Platform_Effect.aspx
http://www.joelonsoftware.com/articles/fog0000000018.html
Вероятно, самая простая форма внедрения зависимости (не смейтесь) - это параметр. Зависимый код зависит от данных, и эти данные вводятся посредством передачи параметра.
Да, это глупо и не затрагивает объектно-ориентированную точку внедрения зависимости, но функциональный программист скажет вам, что (если у вас есть функции первого класса), это единственный вид внедрения зависимости, который вам нужен. Суть в том, чтобы взять тривиальный пример и показать потенциальные проблемы.
Давайте возьмем эту простую традиционную функцию - синтаксис C ++ здесь не имеет значения, но я должен как-то это прописать ...
У меня есть зависимость, которую я хочу извлечь и вставить - текст «Hello World». Достаточно просто ...
Как это более негибко, чем оригинал? Что ж, если я решу, что на выходе должен быть 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
Эти «зависимости» не являются проблемой для первоначального разработчика - каждый, кто использует этот интерфейс, должен посмотреть, что это за зависимости, как они определены и что они означают, и решить, что делать для их приложения. Именно здесь слова «разумные значения по умолчанию» могут сделать жизнь намного проще.
Объектно-ориентированное внедрение зависимостей в принципе не отличается. Написание класса - это непроизводительные затраты как в тексте исходного кода, так и во время разработки, и если этот класс написан для предоставления зависимостей в соответствии с некоторыми спецификациями зависимых объектов, то зависимый объект блокируется для поддержки этого интерфейса, даже если есть необходимость заменить реализацию этого объекта.
Ничто из этого не должно восприниматься как утверждение, что внедрение зависимостей является плохим - это далеко не так. Но любая хорошая техника может применяться чрезмерно и не в том месте. Так же, как не каждая строка должна быть извлечена и превращена в параметр, не все низкоуровневые действия должны быть извлечены из высокоуровневых объектов и превращены в инъекционную зависимость.
источник
Вот моя собственная первоначальная реакция: в основном те же самые недостатки любой модели.
источник
Самым большим «недостатком» Inversion of Control (не совсем DI, но достаточно близко) является то, что он имеет тенденцию устранять наличие единой точки для просмотра обзора алгоритма. Это в основном то, что происходит, когда вы развязываете код, хотя - возможность искать в одном месте - это артефакт тесной связи.
источник
Я не думаю, что такой список существует, однако попробуйте прочитать эти статьи:
Я могу скрыть код (если вы не работаете с хорошей IDE)
Неправильное использование IoC может привести к плохому коду в соответствии с дядей Бобом.
Нужно следить за чрезмерным проектированием и созданием ненужной универсальности.
источник
Последние 6 месяцев я активно использую Guice (Java DI framework). Хотя в целом я думаю, что это здорово (особенно с точки зрения тестирования), есть определенные недостатки. Наиболее заметно:
Теперь, когда я пожаловался. Позвольте мне сказать, что я буду продолжать (охотно) использовать Guice в моем текущем проекте и, скорее всего, в следующем. Внедрение зависимостей - отличный и невероятно мощный паттерн. Но это определенно может сбить с толку, и вы почти наверняка потратите некоторое время на проклятие в любой среде внедрения зависимостей, которую вы выберете.
Кроме того, я согласен с другими авторами, что внедрение зависимостей может быть чрезмерным.
источник
Код без какого-либо DI подвергается общеизвестному риску запутаться в коде спагетти - некоторые признаки состоят в том, что классы и методы слишком велики, делают слишком много и не могут быть легко изменены, разбиты, переработаны или протестированы.
Код с DI, который часто используется, может быть кодом Ravioli, где каждый маленький класс подобен отдельному слепку равиоли - он выполняет одну небольшую вещь и придерживается принципа единой ответственности , что хорошо. Но, глядя на классы сами по себе, трудно понять, что делает система в целом, поскольку это зависит от того, как все эти многочисленные мелкие части сочетаются друг с другом, что трудно увидеть. Это просто похоже на большую кучу мелких вещей.
Избегая сложности спагетти больших кусков связанного кода в большом классе, вы рискуете столкнуться с другим типом сложности, когда существует множество простых маленьких классов и взаимодействия между ними являются сложными.
Я не думаю, что это фатальный недостаток - DI все еще стоит. Некоторая степень стиля равиоли с маленькими классами, которые делают только одну вещь, вероятно хороша. Даже в избытке, я не думаю, что это плохо, как спагетти-код. Но осознание того, что его можно зайти слишком далеко, - это первый шаг к тому, чтобы этого избежать. Перейдите по ссылкам для обсуждения того, как этого избежать.
источник
Если у вас есть доморощенное решение, зависимости прямо перед вами в конструкторе. Или, может быть, в качестве параметров метода, который, опять же, не так сложно определить. Хотя управляемые фреймворком зависимости, если их довести до крайности, могут начать казаться волшебством.
Однако наличие слишком большого количества зависимостей в слишком большом количестве классов является явным признаком того, что ваша структура классов испорчена. Таким образом, внедрение зависимостей (доморощенных или управляемых фреймворками) может помочь выявить вопиющие проблемы проектирования, которые в противном случае могли бы быть скрыты в темноте.
Чтобы лучше проиллюстрировать второй пункт, вот выдержка из этой статьи ( первоисточник ), которая, как я искренне считаю, является фундаментальной проблемой при создании любой системы, а не только компьютерных систем.
DI решает эту проблему? Нет . Но это помогает вам ясно видеть, пытаетесь ли вы делегировать ответственность за проектирование каждой комнаты ее обитателям.
источник
Одна вещь, которая заставляет меня немного поежиться с DI - это предположение, что все внедренные объекты дешевы для создания экземпляров и не вызывают побочных эффектов - ИЛИ - зависимость используется так часто, что она перевешивает любые связанные с этим затраты на создание экземпляров.
Это может быть важно, когда зависимость не часто используется в классе потребления; такие как что-то вроде
IExceptionLogHandlerService
. Очевидно, что такая служба вызывается (надеюсь, :)) редко в классе - предположительно, только при исключениях, которые необходимо регистрировать; все же канонический образец инжектора конструктора ...... требует предоставления "живого" экземпляра этой услуги, черт побери, стоимость / побочные эффекты, необходимые для его получения. Вероятно, это не так, но что, если создание этого экземпляра зависимости потребовало обращения к службе / базе данных, поиска файлов конфигурации или блокировки ресурса до его удаления? Если бы вместо этого этот сервис создавался по мере необходимости, размещался в сервисе или создавался на заводе (у всех были свои проблемы), то вы брали бы стоимость строительства только тогда, когда это необходимо.
В настоящее время общепринятым принципом разработки программного обеспечения является то, что создание объекта является дешевым и не вызывает побочных эффектов. И хотя это хорошее понятие, это не всегда так. Однако использование типичного конструктора-инъекции в основном требует, чтобы это было так. То есть, когда вы создаете реализацию зависимости, вы должны проектировать ее с учетом DI. Возможно, вы бы сделали объектное строительство более дорогостоящим, чтобы получить преимущества в другом месте, но если эта реализация будет внедрена, это, вероятно, заставит вас пересмотреть этот дизайн.
Кстати, некоторые методы могут смягчить эту проблему, позволяя ленивую загрузку внедренных зависимостей, например, предоставляя классу
Lazy<IService>
экземпляр в качестве зависимости. Это изменило бы конструкторы ваших зависимых объектов и сделало бы их еще более осведомленными о деталях реализации, таких как затраты на создание объектов, что, возможно, также нежелательно.источник
this.errorLogger.WriteError(ex)
тогда, когда в операторе try / catch возникает ошибка.Иллюзия того, что вы развязали свой код, просто внедрив зависимость без АКТУАЛЬНОЙ развязки. Я думаю, что это самая опасная вещь в DI.
источник
Это скорее придурок. Но одним из недостатков внедрения зависимостей является то, что инструментам разработки становится немного сложнее рассуждать и ориентироваться в коде.
В частности, если вы нажмете Control-Click / Command-Click при вызове метода в коде, вы перейдете к объявлению метода в интерфейсе вместо конкретной реализации.
Это действительно больше недостатка слабосвязанного кода (кода, разработанного интерфейсом), и применяется, даже если вы не используете внедрение зависимостей (то есть, даже если вы просто используете фабрики). Но появление внедрения зависимостей - это то, что действительно поощряет слабосвязанный код в массы, поэтому я решил упомянуть об этом.
Кроме того, преимущества слабосвязанного кода намного перевешивают это, поэтому я называю это ничтожным. Хотя я работал достаточно долго, чтобы знать, что это своего рода откат, который вы можете получить, если попытаетесь ввести внедрение зависимости.
На самом деле, я рискну предположить, что для каждого «недостатка», который вы можете найти для внедрения зависимости, вы найдете много преимуществ, которые намного перевешивают его.
источник
Внедрение зависимостей на основе конструктора (без помощи магических «структур») - это чистый и полезный способ структурировать ОО-код. В лучших кодовых базах, которые я видел, за годы, проведенные с другими бывшими коллегами Мартина Фаулера, я начал замечать, что большинство хороших классов, написанных таким образом, в конечном итоге имеют один
doSomething
метод.Основной недостаток в том, что, как только вы поймете, что это всего лишь хитрый и длинный способ OO писать замыкания в виде классов, чтобы получить преимущества функционального программирования, ваша мотивация писать OO-код может быстро испариться.
источник
Я обнаружил, что инъекция в конструктор может привести к появлению больших уродливых конструкторов (и я использую их в своей кодовой базе - возможно, мои объекты слишком гранулированы?). Кроме того, иногда с внедрением конструктора я получаю ужасные циклические зависимости (хотя это очень редко), так что вы можете столкнуться с необходимостью иметь какой-то жизненный цикл с готовым состоянием с несколькими циклами внедрения зависимостей в более сложной системе.
Тем не менее, я предпочитаю инъекцию construtor, а не установку setter, потому что, как только мой объект сконструирован, я без сомнения знаю, в каком состоянии он находится, находится ли он в модульном тестовом окружении или загружен в какой-то контейнер IOC. Который, окольным способом, говорит, что я чувствую, что это главный недостаток инъекции сеттера.
(Как примечание, я считаю, что вся тема довольно "религиозная", но ваш пробег будет зависеть от уровня технического фанатизма в вашей команде разработчиков!)
источник
Если вы используете DI без контейнера IOC, самым большим недостатком является то, что вы быстро видите, сколько на самом деле зависимостей в вашем коде и насколько тесно все на самом деле. («Но я подумал, что это хороший дизайн!») Естественное продвижение вперед - это переход к контейнеру IOC, который может занять немного времени для изучения и реализации (не так плохо, как кривая обучения WPF, но это не бесплатно. либо). Последним недостатком является то, что некоторые разработчики начнут писать честные и добрые юнит-тесты, и им потребуется время, чтобы понять это. Разработчики, которые раньше могли что-то провернуть за полдня, внезапно потратят два дня, пытаясь понять, как высмеивать все их зависимости.
Как и в случае с ответом Марка Симанна, суть в том, что вы тратите время на то, чтобы стать лучшим разработчиком, а не взламывать куски кода вместе и бросать его за дверь / в производство. Что бы ваш бизнес предпочел? Только ты можешь ответить на это.
источник
DI - это техника или шаблон, не связанные с какой-либо структурой. Вы можете подключить свои зависимости вручную. DI помогает вам с SR (Единая ответственность) и SoC (разделение интересов). DI приводит к лучшему дизайну. С моей точки зрения и опыта нет минусов . Как и с любым другим шаблоном, вы можете ошибиться или использовать его неправильно (но в случае DI это довольно сложно).
Если вы вводите DI в качестве принципа для унаследованного приложения с использованием фреймворка - самая большая ошибка, которую вы можете сделать, - это неправильно использовать его как Service-Locater. DI + Framework сам по себе великолепен и только улучшал его везде, где я его видел! С организационной точки зрения, есть общие проблемы с каждым новым процессом, техникой, шаблоном, ...:
В общем, вы должны вкладывать время и деньги , кроме того, нет никаких минусов, на самом деле!
источник
Читаемость кода. Вы не сможете легко понять поток кода, поскольку зависимости скрыты в файлах XML.
источник
Две вещи:
Например, IntelliJ (коммерческая версия) поддерживает проверку допустимости конфигурации Spring и будет отмечать ошибки, такие как нарушения типов в конфигурации. Без поддержки такого типа вы не сможете проверить правильность конфигурации перед запуском тестов.
Это одна из причин, почему шаблон «торт» (как он известен сообществу Scala) является хорошей идеей: проводку между компонентами можно проверить с помощью средства проверки типов. У вас нет такой выгоды с аннотациями или XML.
Такие фреймворки, как Spring или Guice, затрудняют статически определить, как будет выглядеть граф объектов, созданный контейнером. Хотя они создают граф объектов при запуске контейнера, они не предоставляют полезных API, которые описывают граф объектов, который / будет / создан.
источник
Кажется, что предполагаемые преимущества статически типизированного языка значительно уменьшаются, когда вы постоянно применяете методы для обхода статической типизации. Один большой Java-магазин, в котором я только что брал интервью, определял их зависимости сборки с помощью статического анализа кода ... который должен был проанализировать все файлы Spring, чтобы быть эффективными.
источник
Это может увеличить время запуска приложения, поскольку контейнер IoC должен правильно разрешать зависимости, а иногда требуется выполнить несколько итераций.
источник