Какое лучшее определение для Dependency Injection?

10

Каждый раз, когда кто-то приходит ко мне и просит меня концептуально определить Dependency Injection и объяснить реальные плюсы и минусы использования DI в разработке программного обеспечения. Признаюсь, у меня есть некоторые трудности с объяснением понятий DI. Каждый раз, когда мне нужно рассказать им историю о едином принципе ответственности, составе по наследству и т. Д.

Кто-нибудь может помочь мне объяснить лучший способ описания DI для разработчиков?

Тьяго Сампайо
источник
2
Проблема в том, что существует очень много противоречивых определений DI. Я придерживаюсь позиции «чистого DI»: если у меня есть функция, которая полагается на свои параметры для предоставления всех состояний, данных и т. Д., То эта функция использует DI. С другой стороны, некоторые будут утверждать, что без структуры DI нет внедрения зависимости (хотя, конечно, они ошибаются;)). Так что, если вы не можете придумать определение, вы не можете начать объяснять, что это такое ...
Дэвид Арно
Так что, как я понимаю, это не только моя проблема.
Tiago Sampaio
1
Связанный: stackoverflow.com/questions/1638919/...
Liath
Все сводится к следующему: внедрение зависимостей является одним из методов, используемых для достижения инверсии зависимостей; все остальное - это просто дополнительный материал, построенный поверх этого. Обратите внимание, что в этих двух терминах слово «зависимость» имеет несколько разные значения. В внедрении зависимости это относится к компоненту, от которого зависит код. В инверсии зависимостей это относится к (направленным) отношениям - тем, которые мы хотим инвертировать. Последнее является целью, поэтому основные плюсы и минусы одинаковы; плюс некоторые дополнительные проблемы, связанные с фактической реализацией, такие как управление временем жизни объекта.
Филипп Милованович

Ответы:

22

Внедрение зависимостей - ужасное имя (IMO) 1 для довольно простой концепции. Вот пример:

  1. У вас есть метод (или класс с методами), который делает X (например, получить данные из базы данных)
  2. Как часть выполнения X, указанный метод создает и управляет внутренним ресурсом (например, a DbContext). Этот внутренний ресурс называется зависимостью.
  3. Вы удаляете создание и управление ресурсом (т. Е. DbContext) Из метода и возлагаете на вызывающего абонента ответственность за предоставление этого ресурса (в качестве параметра метода или при создании экземпляра класса).
  4. Вы сейчас делаете инъекцию зависимости.


[1] : Я родом из низшего уровня, и мне потребовались месяцы, чтобы сесть и выучить внедрение зависимостей, потому что название подразумевает, что это будет что-то гораздо более сложное, например, внедрение DLL . Тот факт, что Visual Studio (и мы в целом разработчики) ссылается на библиотеки .NET (библиотеки DLL или сборки ), от которых зависит проект в качестве зависимостей , не помогает вообще. Существует даже такая вещь, как Ходок Зависимости (зависимость.exe) .


[Править] Я подумал, что какой-то демонстрационный код пригодится для некоторых, так что вот один (в C #).

Без внедрения зависимости:

public class Repository : IDisposable
{
    protected DbContext Context { get; }

    public Repository()
    {
        Context = new DbContext("name=MyEntities");
    }

    public void Dispose()
    {
        Context.Dispose();
    }
}

Ваш потребитель будет делать что-то вроде:

using ( var repository = new Repository() )
{
    // work
}

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

public class RepositoryWithDI
{
    protected DbContext Context { get; }

    public RepositoryWithDI(DbContext context)
    {
        Context = context;
    }
}

Это теперь ОТВЕТСТВЕННОСТЬ абонента создать экземпляр DbContextи передать (errm, инъекционные ) ему в класс:

using ( var context = new DbContext("name=MyEntities") )
{
    var repository = new RepositoryWithDI(context);

    // work
}
Marc.2377
источник
3
Это должно быть добавлено в Википедию.
Evorlor
2
Теперь ответственность вызывающего абонента заключается в создании экземпляра DbContext - я думаю, что это будет обязанностью точки входа приложения создавать все необходимые зависимости. Таким образом, потребителю нужно только ввести требуемый тип в качестве зависимости в собственном договоре.
Фабио
@ Фабио Это может быть. (В этом случае ответственность вызывающего абонента заключается в предоставлении ресурса, который был создан при запуске приложения, вызываемому методу / классу.) Однако в моем примере это не так, потому что нет необходимости объяснять концепцию внедрения зависимости ,
Marc.2377
5

Абстрактные понятия часто лучше объяснить с помощью реальной аналогии. Это моя аналогия:

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

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

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

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

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

Flater
источник
1
Мне это нравится, но ОП ищет объяснения для разработчиков . Начальная абстракция хороша, но рано или поздно ей понадобится реальный пример из жизни.
Робби Ди
1
@RobbieDee: Когда цель шаблона ясна, его реализация также становится понятной. Например, ответ Марка абсолютно верен, но я чувствую, что объяснение застревает из-за сложной природы примера ситуации, которую он использует. Это сводится к следующему: «Если вы хотите построить корабль, не подбрасывайте людей, чтобы собирать дрова и не назначайте им задачи и работу, а скорее учите их тосковать по бесконечной необъятности моря». , Вместо того, чтобы объяснять, что делать, я предпочитаю объяснять, зачем это делать.
Флейтер
2
Вы, конечно, правы, но я не могу помочь, но думаю, что мне нужен реальный пример - такой как отсутствие реальной файловой системы или базы данных, чтобы подогреть мой аппетит, но, возможно, это всего лишь мое узкое мнение разработчика :)
Робби Ди
1

Простой ответ на это:

Прежде всего, класс должен иметь четко определенную ответственность, и все, что находится за пределами этой области, должно быть за пределами этого класса. С учетом вышесказанного, Dependency Injection - это когда вы внедряете функциональность из другого класса B в класс A, используя помощь «третьей стороны» для достижения такого разделения интересов, помогая классу A выполнить некоторую операцию, выходящую за пределы его области видимости.

.Net Core - довольно хороший пример, который вы можете привести, потому что в этой среде много внедрения зависимостей. Как правило, сервисы, которые вы хотите внедрить, находятся в startup.csфайле.

Конечно, студент должен знать некоторые понятия, такие как полиморфизм, интерфейсы и принципы ООП-дизайна.

Фернандо Калазанс
источник
0

Вокруг этого, по сути, простого понятия, много пуха и пустышек.

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

Это определение, которое я лично использую:

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

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

Фреймворки, такие как moq, позволяют определять двойные значения (симулировать версии Y) с помощью интерфейса, так что можно внедрять в экземпляр Y, где Y, например, соединение с базой данных.

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

Робби Ди
источник
0

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

Стратегия Pattern является отличным примером внедрения зависимости.

ShinEmperor
источник
0

Чтобы сделать это правильно, мы должны сначала определить зависимости и внедрение.

  • Зависимость: любой ресурс, в котором нуждается операция.
  • Инъекция: передача этого ресурса в операцию, как правило, в качестве аргумента метода.

Примитивным примером будет метод, который добавляет два значения. Очевидно, что этот метод нуждается в добавлении значений. Если они предоставляются путем передачи их в качестве аргументов, это уже будет случай внедрения зависимости. Альтернативой может быть реализация операндов в качестве свойств или глобальных переменных. Таким образом, никакие зависимости не будут внедрены, зависимости будут доступны извне заранее.

Предположим, вы используете вместо этого свойства и называете их A и B. Если вы измените имена на Op1 и Op2, вы нарушите метод Add. В противном случае ваша IDE обновит все имена для вас, суть в том, что метод также должен быть обновлен, поскольку он имеет зависимости от внешних ресурсов.

Этот пример является базовым, но вы можете представить более сложные примеры, когда метод выполняет операцию над объектом, таким как изображение, или когда он читает из потока файлов. Вы хотите, чтобы метод достиг изображения, требуя, чтобы он знал, где он находится? Нет. Вы хотите, чтобы метод открыл сам файл, требуя, чтобы он знал, где искать файл, или даже знал, что он будет читать из файла? Нет.

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

Преимущества: так как зависимости для среды метода были устранены, изменения в методе не будут влиять на среду и наоборот. => Приложение становится проще в обслуживании (изменении).

Мартин Маат
источник