Я пытаюсь понять инъекции зависимости (DI), и снова я потерпел неудачу. Это просто кажется глупым. Мой код никогда не беспорядок; Я почти не пишу виртуальные функции и интерфейсы (хотя я делаю это однажды в голубой луне), и вся моя конфигурация волшебным образом сериализуется в класс с использованием json.net (иногда с использованием сериализатора XML).
Я не совсем понимаю, какую проблему это решает. Это выглядит как способ сказать: «Привет. Когда вы запускаете эту функцию, возвращаете объект этого типа, который использует эти параметры / данные».
Но ... зачем мне это использовать? Заметьте, мне также никогда не приходилось пользоваться object
, но я понимаю, для чего это нужно .
Каковы реальные ситуации при создании веб-сайта или настольного приложения, где можно использовать DI? Я могу легко придумать случаи, когда кто-то может захотеть использовать интерфейсы / виртуальные функции в игре, но это крайне редко (достаточно редко, чтобы я не мог вспомнить ни одного случая), чтобы использовать это в неигровом коде.
источник
Ответы:
Во-первых, я хочу объяснить предположение, которое я делаю для этого ответа. Это не всегда так, но довольно часто
(На самом деле, существуют интерфейсы, которые также являются существительными, но я хочу обобщить их здесь.)
Так, например, интерфейс может быть чем-то вроде
IDisposable
,IEnumerable
илиIPrintable
. Класс - это фактическая реализация одного или нескольких из этих интерфейсов:List
или ониMap
могут быть обеими реализациямиIEnumerable
.Чтобы понять: часто ваши занятия зависят друг от друга. Например, у вас может быть
Database
класс, который обращается к вашей базе данных (ха, сюрприз! ;-)), но вы также хотите, чтобы этот класс регистрировал доступ к базе данных. Предположим, у вас есть другой классLogger
, затемDatabase
есть зависимость отLogger
.Все идет нормально.
Вы можете смоделировать эту зависимость внутри вашего
Database
класса с помощью следующей строки:и все хорошо. Это хорошо до того дня, когда вы поймете, что вам нужно несколько регистраторов: иногда вы хотите войти в консоль, иногда в файловую систему, иногда используя TCP / IP и удаленный сервер журналирования, и так далее ...
И, конечно же, вы НЕ хотите менять весь свой код (пока у вас есть его миллиарды) и заменять все строки
по:
Во-первых, это не весело. Во-вторых, это подвержено ошибкам. В-третьих, это глупая, повторяющаяся работа для обученной обезьяны. Ну так что ты делаешь?
Очевидно, что неплохо было бы представить интерфейс
ICanLog
(или аналогичный), который реализован всеми различными регистраторами. Итак, шаг 1 в вашем коде - это то, что вы делаете:Теперь вывод типа больше не меняет тип, у вас всегда есть один единственный интерфейс для разработки. Следующим шагом является то, что вы не хотите иметь
new Logger()
снова и снова. Таким образом, вы наделяете надежность создания новых экземпляров одним центральным фабричным классом и получаете код, такой как:Фабрика сама решает, какой именно регистратор создать. Ваш код больше не заботится, и если вы хотите изменить тип используемого регистратора, вы измените его один раз : внутри завода.
Теперь, конечно, вы можете обобщить эту фабрику и заставить ее работать для любого типа:
Где-то эта TypeFactory нуждается в данных конфигурации, какой фактический класс должен быть создан при запросе определенного типа интерфейса, поэтому вам необходимо сопоставление. Конечно, вы можете сделать это отображение внутри вашего кода, но тогда изменение типа означает перекомпиляцию. Но вы также можете поместить это отображение в файл XML, например. Это позволяет вам изменять фактически используемый класс даже после времени компиляции (!), Что означает динамически, без перекомпиляции!
Чтобы дать вам полезный пример для этого: подумайте о программном обеспечении, которое не регистрируется нормально, но когда ваш клиент звонит и просит помощи, потому что у него есть проблема, вы отправляете ему только обновленный файл конфигурации XML, и теперь у него есть ведение журнала включено, и ваша поддержка может использовать файлы журнала, чтобы помочь вашему клиенту.
И теперь, когда вы немного заменяете имена, вы получаете простую реализацию локатора служб , которая является одним из двух шаблонов инверсии управления (поскольку вы инвертируете контроль над тем, кто решает, какой именно класс создать).
В целом это уменьшает зависимости в вашем коде, но теперь весь ваш код имеет зависимость от центрального, единого локатора службы.
Внедрение зависимостей теперь является следующим шагом в этой строке: просто избавьтесь от этой единственной зависимости от локатора службы: вместо того, чтобы различные классы запрашивали у локатора службы реализацию для определенного интерфейса, вы - опять же - возвращаете контроль над тем, кто и как создает ,
С внедрением зависимостей ваш
Database
класс теперь имеет конструктор, который требует параметр типаICanLog
:Теперь ваша база данных всегда имеет регистратор для использования, но она больше не знает, откуда этот регистратор.
И вот здесь вступает в игру инфраструктура DI: вы снова настраиваете свои отображения, а затем просите свою инфраструктуру DI создать для вас экземпляр приложения. Поскольку
Application
класс требуетICanPersistData
реализации, экземплярDatabase
внедряется - но для этого он должен сначала создать экземпляр типа регистратора, для которого настроенICanLog
. И так далее ...Короче говоря: внедрение зависимостей - это один из двух способов удаления зависимостей в вашем коде. Это очень полезно для изменения конфигурации после компиляции и отлично подходит для модульного тестирования (поскольку позволяет очень легко вводить заглушки и / или макеты).
На практике есть вещи, которые вы не можете обойтись без локатора службы (например, если вы заранее не знаете, сколько экземпляров вам нужно для конкретного интерфейса: платформа DI всегда вводит только один экземпляр для каждого параметра, но вы можете вызвать локатор службы внутри цикла, разумеется), поэтому чаще всего каждая структура DI также предоставляет локатор службы.
Но в основном это все.
PS: То, что я описал здесь, - это метод, называемый инжекцией конструктора , также есть инъекция свойства, где не параметры конструктора, а свойства используются для определения и разрешения зависимостей. Думайте о внедрении свойства как о необязательной зависимости, а о внедрении конструктора как об обязательной зависимости. Но обсуждение этого вопроса выходит за рамки этого вопроса.
источник
Я думаю, что много раз людей путаются о разнице между инъекцией зависимостей и инъекционной зависимостью структурой (или контейнером , как его часто называют).
Внедрение зависимостей - очень простая концепция. Вместо этого кода:
Вы пишете код так:
И это все. Шутки в сторону. Это дает вам массу преимуществ. Двумя важными являются способность контролировать функциональность из центрального места (
Main()
функции) вместо того, чтобы распространять ее по всей программе, и возможность более легко тестировать каждый класс изолированно (потому что вы можете вместо этого передавать макеты или другие фальшивые объекты в его конструктор реальной стоимости).Недостатком, конечно, является то, что теперь у вас есть одна мега-функция, которая знает обо всех классах, используемых вашей программой. Это то, с чем могут помочь DI-фреймворки. Но если у вас возникли проблемы с пониманием того, почему этот подход полезен, я бы рекомендовал сначала начать с ручного внедрения зависимостей, чтобы вы могли лучше оценить, что различные инфраструктуры могут сделать для вас.
источник
Как говорилось в других ответах, внедрение зависимостей - это способ создания ваших зависимостей вне класса, который их использует. Вы вводите их извне и контролируете их создание изнутри вашего класса. Именно поэтому внедрение зависимостей является реализацией принципа инверсии управления (IoC).
IoC - принцип, где DI - шаблон. Насколько мне известно, причина, по которой вам может «понадобиться более одного регистратора», никогда не встречается, но на самом деле причина в том, что вам это действительно нужно, когда вы что-то тестируете. Пример:
Моя особенность:
Вы можете проверить это так:
Так что где-то в
OfferWeasel
, он строит вам объект предложения, как это:Проблема здесь в том, что этот тест, скорее всего, всегда будет неудачным, поскольку устанавливаемая дата будет отличаться от заявленной, даже если вы просто
DateTime.Now
введете код теста, он может быть отключен на пару миллисекунд и поэтому всегда терпеть неудачу. Лучшим решением сейчас было бы создать интерфейс для этого, который позволит вам контролировать, какое время будет установлено:Интерфейс - это абстракция. Одна из них - РЕАЛЬНАЯ вещь, а другая позволяет вам подделывать какое-то время там, где это необходимо. Затем тест можно изменить следующим образом:
Таким образом, вы применили принцип «инверсии управления», введя зависимость (получая текущее время). Основная причина сделать это для облегчения изолированного модульного тестирования, есть другие способы сделать это. Например, интерфейс и класс здесь не нужны, поскольку в C # функции могут передаваться как переменные, поэтому вместо интерфейса вы можете использовать a
Func<DateTime>
для достижения того же самого. Или, если вы используете динамический подход, вы просто передаете любой объект, который имеет эквивалентный метод ( типизирование по типу утки ), и вам вообще не нужен интерфейс.Вам вряд ли понадобится более одного регистратора. Тем не менее, внедрение зависимостей необходимо для статически типизированного кода, такого как Java или C #.
И ... Следует также отметить, что объект может правильно выполнять свое назначение только во время выполнения, если все его зависимости доступны, поэтому при настройке внедрения свойств мало что нужно. На мой взгляд, все зависимости должны быть удовлетворены при вызове конструктора, так что инъекция в конструктор - это то, что нужно.
Я надеюсь, что это помогло.
источник
Я думаю, что классический ответ заключается в создании приложения с большей развязкой, которое не знает, какая реализация будет использоваться во время выполнения.
Например, мы являемся центральным поставщиком платежей, работающим со многими поставщиками платежей по всему миру. Однако, когда сделан запрос, я понятия не имею, какому платежному процессору я собираюсь позвонить. Я мог бы запрограммировать один класс с кучей переключателей, таких как:
Теперь представьте, что теперь вам нужно хранить весь этот код в одном классе, потому что он не отделен должным образом, вы можете представить, что для каждого нового процессора, который вы будете поддерживать, вам нужно будет создать новый регистр if // для с каждым методом это только усложняется, однако, используя Dependency Injection (или Inversion of Control - как его иногда называют, что означает, что тот, кто контролирует выполнение программы, известен только во время выполнения, а не усложняется), вы можете достичь чего-то очень аккуратный и ремонтопригодный.
** код не скомпилируется, я знаю :)
источник
new ThatProcessor()
а не использовал бы фреймворкОсновная причина использования DI заключается в том, что вы хотите возложить ответственность за знания реализации, где знания есть. Идея DI очень тесно связана с инкапсуляцией и дизайном через интерфейс. Если внешний интерфейс запрашивает у какого-либо данных какие-либо данные, то для внешнего интерфейса неважно, как серверный блок решает этот вопрос. Это до обработчика запросов.
Это уже часто встречается в ООП. Много раз создавая такие фрагменты кода, как:
Недостатком является то, что класс реализации все еще жестко закодирован, следовательно, имеет интерфейс, который знает, какая реализация используется. DI продвигает проектирование через интерфейс еще на один шаг, и единственное, что нужно знать интерфейсу, - это знание интерфейса. Между DYI и DI находится шаблон локатора службы, поскольку внешний интерфейс должен предоставить ключ (присутствующий в реестре локатора службы), чтобы его запрос стал разрешенным. Пример поиска службы:
Пример DI:
Одним из требований DI является то, что контейнер должен иметь возможность выяснить, какой класс является реализацией какого интерфейса. Следовательно, для контейнера DI требуется строго типизированный дизайн и только одна реализация для каждого интерфейса одновременно. Если вам нужно больше реализаций интерфейса в одно и то же время (например, калькулятор), вам нужен сервисный локатор или шаблон проектирования фабрики.
D (b) I: Внедрение зависимостей и проектирование по интерфейсу. Это ограничение не является большой практической проблемой. Преимущество использования D (b) I заключается в том, что он служит для связи между клиентом и поставщиком. Интерфейс - это перспектива объекта или набора поведений. Последнее имеет решающее значение здесь.
Я предпочитаю администрирование сервисных контрактов вместе с D (b) I в кодировании. Они должны идти вместе. Использование D (b) I в качестве технического решения без организационного администрирования сервисных контрактов, на мой взгляд, не очень выгодно, потому что DI - это просто дополнительный уровень инкапсуляции. Но когда вы можете использовать это вместе с организационным администрированием, вы действительно можете использовать организационный принцип D (b), который я предлагаю. Это может помочь вам в долгосрочной перспективе структурировать взаимодействие с клиентом и другими техническими отделами по таким темам, как тестирование, управление версиями и разработка альтернатив. Когда у вас есть неявный интерфейс, как в жестко закодированном классе, он становится намного менее общительным со временем, чем когда вы делаете его явным, используя D (b) I. Все сводится к обслуживанию, которое происходит со временем, а не за один раз. :-)
источник