[Design-Patterns] Что такое инъекция зависимости?


Answers

Лучшее определение, которое я нашел до сих пор, - это Джеймс Шор :

«Инъекция зависимостей» - это 25-долларовый термин для концепции 5 центов. [...] Инъекция зависимости означает предоставление объекту своих переменных экземпляра. [...].

Есть статья Мартина Фаулера, которая может оказаться полезной.

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

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

Question

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

Что такое инъекция зависимости и когда / почему ее следует использовать или не следует использовать?




Any nontrivial application is made up of two or more classes that collaborate with each other to perform some business logic. Traditionally, each object is responsible for obtaining its own references to the objects it collaborates with (its dependencies). When applying DI, the objects are given their dependencies at creation time by some external entity that coordinates each object in the system. In other words, dependencies are injected into objects.

For further details please see enter link description here




I think since everyone has written for DI, let me ask a few questions..

  1. When you have a configuration of DI where all the actual implementations(not interfaces) that are going to be injected into a class (for eg services to a controller) why is that not some sort of hard-coding?
  2. What if I want to change the object at runtime? For example, my config already says when I instantiate MyController, inject for FileLogger as ILogger. But I might want to inject DatabaseLogger.
  3. Everytime I want to change what objects my AClass needs, I need to now look into two places - The class itself and the configuration file. How does that make life easier?
  4. If Aproperty of AClass is not injected, is it harder to mock it out?
  5. Going back to the first question. If using new object() is bad, how come we inject the implementation and not the interface? I think a lot of you are saying we're in fact injecting the interface but the configuration makes you specify the implementation of that interface ..not at runtime .. it is hardcoded during compile time.

This is based on the answer @Adam N posted.

Why does PersonService no longer have to worry about GroupMembershipService? You just mentioned GroupMembership has multiple things(objects/properties) it depends on. If GMService was required in PService, you'd have it as a property. You can mock that out regardless of whether you injected it or not. The only time I'd like it to be injected is if GMService had more specific child classes, which you wouldn't know until runtime. Then you'd want to inject the subclass. Or if you wanted to use that as either singleton or prototype. To be honest, the configuration file has everything hardcoded as far as what subclass for a type (interface) it is going to inject during compile time.

РЕДАКТИРОВАТЬ

A nice comment by Jose Maria Arranz on DI

DI increases cohesion by removing any need to determine the direction of dependency and write any glue code.

False. The direction of dependencies is in XML form or as annotations, your dependencies are written as XML code and annotations. XML and annotations ARE source code.

DI reduces coupling by making all of your components modular (ie replacable) and have well-defined interfaces to each other.

False. You do not need a DI framework to build a modular code based on interfaces.

About replaceable: with a very simple .properties archive and Class.forName you can define wich classes can change. If ANY class of your code can be changed, Java is not for you, use an scripting language. By the way: annotations cannot be changed without recompiling.

In my opinion there is one only reason for DI frameworks: boiler plate reduction. With a well done factory system you can do the same, more controlled and more predictable as your preferred DI framework, DI frameworks promise code reduction (XML and annotations are source code too). The problem is this boiler plate reduction is just real in very very simple cases (one instance-per class and similar), sometimes in the real world picking the appropriated service object is not as easy as mapping a class to a singleton object.




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

Например, рассмотрим эти положения:

public class PersonService {
  public void addManager( Person employee, Person newManager ) { ... }
  public void removeManager( Person employee, Person oldManager ) { ... }
  public Group getGroupByManager( Person manager ) { ... }
}

public class GroupMembershipService() {
  public void addPersonToGroup( Person person, Group group ) { ... }
  public void removePersonFromGroup( Person person, Group group ) { ... }
} 

В этом примере для реализации PersonService::addManager и PersonService::removeManager понадобится экземпляр PersonService::removeManager для выполнения его работы. Без Injection Dependency традиционным способом выполнения этого было бы создать экземпляр нового GroupMembershipService в конструкторе PersonService и использовать атрибут экземпляра в обеих функциях. Однако, если конструктор GroupMembershipService имеет несколько вещей, которые он требует, или, что еще хуже, есть некоторые инициализационные «сеттеры», которые необходимо вызвать в GroupMembershipService , код растет довольно быстро, а PersonService теперь зависит не только от GroupMembershipService но и также все, от чего зависит GroupMembershipService . Кроме того, привязка к GroupMembershipService жестко закодирована в PersonService что означает, что вы не можете « PersonService для целей тестирования или использовать шаблон стратегии в разных частях приложения.

В случае инъекции зависимостей вместо создания экземпляра GroupMembershipService в вашем PersonService вы либо передадите его в конструктор PersonService , либо добавите свойство (getter и setter), чтобы установить его локальный экземпляр. Это означает, что вашему PersonService больше не нужно беспокоиться о том, как создать GroupMembershipService , он просто принимает те, которые ему даны, и работает с ними. Это также означает, что все, что является подклассом GroupMembershipService , или реализует интерфейс PersonService , может быть «введено» в PersonService , а PersonService не нужно знать об этом изменении.




The whole point of Dependency Injection (DI) is to keep application source code clean and stable :

  • clean of dependency initialization code
  • stable regardless of dependency used

Practically, every design pattern separates concerns to make future changes affect minimum files.

The specific domain of DI is delegation of dependency configuration and initialization.

Example: DI with shell script

If you occasionally work outside of Java, recall how source is often used in many scripting languages (Shell, Tcl, etc., or even import in Python misused for this purpose).

Consider simple dependent.sh script:

#!/bin/sh
# Dependent
touch         "one.txt" "two.txt"
archive_files "one.txt" "two.txt"

The script is dependent: it won't execute successfully on its own ( archive_files is not defined).

You define archive_files in archive_files_zip.sh implementation script (using zip in this case):

#!/bin/sh
# Dependency
function archive_files {
    zip files.zip "$@"
}

Instead of source -ing implementation script directly in the dependent one, you use an injector.sh "container" which wraps both "components":

#!/bin/sh 
# Injector
source ./archive_files_zip.sh
source ./dependent.sh

The archive_files dependency has just been injected into dependent script.

You could have injected dependency which implements archive_files using tar or xz .

Example: removing DI

If dependent.sh script used dependencies directly, the approach would be called dependency lookup (which is opposite to dependency injection ):

#!/bin/sh
# Dependent

# dependency look-up
source ./archive_files_zip.sh

touch         "one.txt" "two.txt"
archive_files "one.txt" "two.txt"

Now the problem is that dependent "component" has to perform initialization itself.

The "component"'s source code is neither clean nor stable because every changes in initialization of dependencies requires new release for "components"'s source code file as well.

Last words

DI is not as largely emphasized and popularized as in Java frameworks.

But it's a generic approach to split concerns of:

  • application development ( single source code release lifecycle)
  • application deployment ( multiple target environments with independent lifecycles)

Using configuration only with dependency lookup does not help as number of configuration parameters may change per dependency (eg new authentication type) as well as number of supported types of dependencies (eg new database type).




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

public class Car
{
    public Car()
    {
        GasEngine engine = new GasEngine();
        engine.Start();
    }
}

public class GasEngine
{
    public void Start()
    {
        Console.WriteLine("I use gas as my fuel!");
    }
}

И чтобы создать экземпляр класса Car, мы будем использовать следующий код:

Car car = new Car();

Проблема с этим кодом, который мы тесно связали с GasEngine, и если мы решим изменить его на ElectricityEngine, тогда нам нужно будет переписать класс Car. И чем больше приложение, тем больше проблем и головной боли нам придется добавлять и использовать новый тип движка.

Другими словами, при таком подходе наш класс высокого класса Car зависит от класса GasEngine более низкого уровня, который нарушает принцип инверсии зависимостей (DIP) от SOLID. DIP предполагает, что мы должны зависеть от абстракций, а не от конкретных классов. Итак, чтобы удовлетворить это, мы вводим интерфейс IEngine и переписываем код, как показано ниже:

    public interface IEngine
    {
        void Start();
    }

    public class GasEngine : IEngine
    {
        public void Start()
        {
            Console.WriteLine("I use gas as my fuel!");
        }
    }

    public class ElectricityEngine : IEngine
    {
        public void Start()
        {
            Console.WriteLine("I am electrocar");
        }
    }

    public class Car
    {
        private readonly IEngine _engine;
        public Car(IEngine engine)
        {
            _engine = engine;
        }

        public void Run()
        {
            _engine.Start();
        }
    }

Теперь наш класс Car зависит только от интерфейса IEngine, а не от конкретной реализации движка. Теперь единственным трюком является то, как мы создаем экземпляр автомобиля и даем ему настоящий конкретный класс двигателя, такой как GasEngine или ElectricityEngine. Вот где входит инъекция зависимостей .

   Car gasCar = new Car(new GasEngine());
   gasCar.Run();
   Car electroCar = new Car(new ElectricityEngine());
   electroCar.Run();

Здесь мы в основном вводим (передаем) нашу зависимость (экземпляр Engine) конструктору Car. Итак, теперь наши классы имеют свободную связь между объектами и их зависимостями, и мы можем легко добавлять новые типы двигателей, не меняя класс Car.

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

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

Также, когда у нас много зависимостей, очень полезно использовать контейнеры Inversion of Control (IoC), которые мы можем определить, какие интерфейсы должны быть сопоставлены с конкретными реализациями для всех наших зависимостей, и мы можем разрешить им эти зависимости для нас, когда они строят наш объект. Например, мы можем указать в отображении для контейнера IoC, что зависимость IEngine должна быть сопоставлена ​​с классом GasEngine, и когда мы спросим контейнер IoC для экземпляра нашего класса Car , он автоматически построит наш класс Car с зависимостью GasEngine прошел.

ОБНОВЛЕНИЕ: Недавно просмотрел курс о EF Core от Джули Лерман, а также понравилось ее краткое определение о DI.

Включение зависимостей - это шаблон, позволяющий вашему приложению вводить объекты «на лету» в классы, которые им нужны, не заставляя эти классы отвечать за эти объекты. Это позволяет вашему коду быть более слабо связанными, а Entity Framework Core подключается к этой же системе сервисов.




Что такое инъекция зависимостей (DI)?

Как говорили другие, Dependency Injection (DI) снимает ответственность за непосредственное создание и управление продолжительностью жизни других экземпляров объектов, от которых зависит наш класс интересов (потребительский класс) (в смысле UML ). Эти экземпляры вместо этого передаются в наш потребительский класс, как правило, в качестве параметров конструктора или через средства определения свойств (управление экземпляром объекта зависимостей и переход к классу потребителей обычно выполняется контейнером инверсии управления (IoC) , но это еще одна тема) ,

DI, DIP и SOLID

В частности, в парадигме принципов SOLID, основанных на объектно-ориентированном дизайне Роберта К. Мартина, DI является одной из возможных реализаций принципа инверсии зависимостей (DIP) . DIP - это D SOLID mantra - другие реализации DIP включают в себя локатор сервисов и шаблоны плагинов.

Цель DIP состоит в том, чтобы разделить жесткие, конкретные зависимости между классами, а вместо этого ослабить связь посредством абстракции, которая может быть достигнута с помощью interface , abstract class или pure virtual class зависимости от используемого языка и подхода ,

Без DIP наш код (я назвал этот «потребительский класс») напрямую связан с конкретной зависимостью, а также часто обременен ответственностью за знание того, как получить и управлять экземпляром этой зависимости, то есть концептуально:

"I need to create/use a Foo and invoke method `GetBar()`"

Принимая во внимание, что после применения DIP требование ослабевает, а проблема получения и управления продолжительностью жизни Foo была устранена:

"I need to invoke something which offers `GetBar()`"

Зачем использовать DIP (и DI)?

Развязанные зависимости между классами таким образом позволяют легко подменять эти классы зависимостей на другие реализации, которые также выполняют предпосылки абстракции (например, зависимость может быть переключена с другой реализацией того же интерфейса). Более того, как отмечали другие, возможно, наиболее распространенная причина для развязки классов через DIP - это позволить тестировать класс потребления отдельно, так как эти же зависимости теперь можно окутать и / или высмеять.

Одним из следствий DI является то, что управление жизненным циклом экземпляров объекта зависимостей больше не контролируется классом потребления, так как объект зависимостей теперь передается в класс потребления (через инсталляцию конструктора или сеттера).

Это можно посмотреть по-разному:

  • Если необходимо сохранить контроль жизненных циклов потребляющим классом, управление можно восстановить, введя (абстрактный) завод для создания экземпляров класса зависимостей в класс потребителя. Потребитель сможет получить экземпляры через Create на фабрике по мере необходимости и избавиться от этих экземпляров после завершения.
  • Или управление жизненным циклом экземпляров зависимостей может быть отказано от контейнера IoC (подробнее об этом ниже).

Когда использовать DI?

  • Там, где, вероятно, потребуется заменить зависимость для эквивалентной реализации,
  • Каждый раз, когда вам нужно будет тестировать методы класса в зависимости от его зависимостей,
  • В тех случаях, когда неопределенность в отношении продолжительности жизни зависит от экспериментов (например, Hey, MyDepClass является потокобезопасным - что, если мы сделаем его MyDepClass введем один и тот же экземпляр всем потребителям?)

пример

Вот простая реализация C #. Учитывая нижний класс потребления:

public class MyLogger
{
   public void LogRecord(string somethingToLog)
   {
      Console.WriteLine("{0:HH:mm:ss} - {1}", DateTime.Now, somethingToLog);
   }
}

Несмотря на кажущуюся безобидность, он имеет две static зависимости от двух других классов: System.DateTime и System.Console , которые не только ограничивают параметры вывода журнала (вход в консоль бесполезен, если никто не смотрит), но, что еще хуже, для автоматического тестирования с учетом зависимости от недетерминированных системных часов.

Тем не менее мы можем применить DIP к этому классу, абстрагировав озабоченность временной MyLogger как зависимость и MyLogger только с простым интерфейсом:

public interface IClock
{
    DateTime Now { get; }
}

Мы также можем ослабить зависимость от Console до абстракции, например TextWriter . Инъекция зависимостей обычно реализуется как либо впрыск constructor (передача абстракции в зависимость как параметр конструктору потребляющего класса), так и setXyz() Setter Injection (передача зависимости с помощью setXyz() setter или .Net Property с {set;} определены). Предпочтение от конструктора предпочтительнее, так как это гарантирует, что класс будет в правильном состоянии после построения и позволит полям внутренней зависимости быть помечены как readonly (C #) или final (Java). Поэтому, используя инъекцию конструктора в приведенном выше примере, это оставляет нам:

public class MyLogger : ILogger // Others will depend on our logger.
{
    private readonly TextWriter _output;
    private readonly IClock _clock;

    // Dependencies are injected through the constructor
    public MyLogger(TextWriter stream, IClock clock)
    {
        _output = stream;
        _clock = clock;
    }

    public void LogRecord(string somethingToLog)
    {
        // We can now use our dependencies through the abstraction 
        // and without knowledge of the lifespans of the dependencies
        _output.Write("{0:yyyy-MM-dd HH:mm:ss} - {1}", _clock.Now, somethingToLog);
    }
}

(Необходимо предоставить конкретные Clock , которые, конечно же, могут вернуться к DateTime.Now , и две зависимости должны быть предоставлены контейнером IoC через впрыск конструктора)

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

[Test]
public void LoggingMustRecordAllInformationAndStampTheTime()
{
    // Arrange
    var mockClock = new Mock<IClock>();
    mockClock.Setup(c => c.Now).Returns(new DateTime(2015, 4, 11, 12, 31, 45));
    var fakeConsole = new StringWriter();

    // Act
    new MyLogger(fakeConsole, mockClock.Object)
        .LogRecord("Foo");

    // Assert
    Assert.AreEqual("2015-04-11 12:31:45 - Foo", fakeConsole.ToString());
}

Следующие шаги

Инъекция зависимостей всегда связана с контейнером «Инверсия управления» (IoC) , для ввода (предоставления) конкретных экземпляров зависимостей и для управления экземплярами lifespan. Во время процесса конфигурирования / начальной загрузки контейнеры IoC позволяют определить следующее:

  • сопоставление между каждой абстракцией и сконфигурированной конкретной реализацией (например, «в любое время, когда потребитель запрашивает IBar , возвращает экземпляр ConcreteBar » )
  • политики могут быть настроены для управления жизненным циклом каждой зависимости, например, для создания нового объекта для каждого экземпляра потребителя, для совместного использования экземпляра зависимости Singleton для всех потребителей, для совместного использования одного экземпляра зависимостей только через один и тот же поток и т. д.
  • В .Net контейнеры IoC знают о протоколах, таких как IDisposable и берут на себя ответственность за Disposing зависимостей в соответствии с настроенным управлением продолжительностью жизни.

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

Ключом к DI-дружественному коду является предотвращение статической связи классов, а не использование new () для создания зависимостей

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

Но преимуществ много, особенно в способности тщательно протестировать ваш класс интересов.

Примечание : создание / сопоставление / проектирование (через new ..() ) POCO / POJO / сериализации DTOs / диаграммы сущностей / анонимные прогнозы JSON и др., Т.е. классы или записи только для данных, используемые или возвращенные из методов, не рассматриваются как зависимостей (в смысле UML) и не подвержены DI. Using new to project these is just fine.







Dependency Injection means a way (actually any-way ) for one part of code (eg a class) to have access to dependencies (other parts of code, eg other classes, it depends upon) in a modular way without them being hardcoded (so they can change or be overriden freely, or even be loaded at another time, as needed)

(and ps , yes it has become an overly-hyped 25$ name for a rather simple, concept) , my .25 cents




The best analogy I can think of is the surgeon and his assistant(s) in an operation theater, where the surgeon is the main person and his assistant who provides the various surgical components when he needs it so that the surgeon can concentrate on the one thing he does best (surgery). Without the assistant the surgeon has to get the components himself every time he needs one.

DI for short, is a technique to remove a common additional responsibility (burden) on components to fetch the dependent components, by providing them to it.

DI brings you closer to the Single Responsibility (SR) principle, like the surgeon who can concentrate on surgery .

When to use DI : I would recommend using DI in almost all production projects ( small/big), particularly in ever changing business environments :)

Why : Because you want your code to be easily testable, mockable etc so that you can quickly test your changes and push it to the market. Besides why would you not when you there are lots of awesome free tools/frameworks to support you in your journey to a codebase where you have more control.




from Book Apress.Spring.Persistence.with.Hibernate.Oct.2010

The purpose of dependency injection is to decouple the work of resolving external software components from your application business logic.Without dependency injection, the details of how a component accesses required services can get muddled in with the component's code. This not only increases the potential for errors, adds code bloat, and magnifies maintenance complexities; it couples components together more closely, making it difficult to modify dependencies when refactoring or testing.




The popular answers are unhelpful, because they define dependency injection in a way that isn't useful. Let's agree that by "dependency" we mean some pre-existing other object that our object X needs. But we don't say we're doing "dependency injection" when we say

$foo = Foo->new($bar);

We just call that passing parameters into the constructor. We've been doing that regularly ever since constructors were invented.

"Dependency injection" is considered a type of "inversion of control", which means that some logic is taken out of the caller. That isn't the case when the caller passes in parameters, so if that were DI, DI would not imply inversion of control.

DI means there is an intermediate level between the caller and the constructor which manages dependencies. A Makefile is a simple example of dependency injection. The "caller" is the person typing "make bar" on the command line, and the "constructor" is the compiler. The Makefile specifies that bar depends on foo, and it does a

gcc -c foo.cpp; gcc -c bar.cpp

before doing a

gcc foo.o bar.o -o bar

The person typing "make bar" doesn't need to know that bar depends on foo. The dependency was injected between "make bar" and gcc.

The main purpose of the intermediate level is not just to pass in the dependencies to the constructor, but to list all the dependencies in just one place , and to hide them from the coder (not to make the coder provide them).

Usually the intermediate level provides factories for the constructed objects, which must provide a role that each requested object type must satisfy. That's because by having an intermediate level that hides the details of construction, you've already incurred the abstraction penalty imposed by factories, so you might as well use factories.




Dependency injection is the heart of the concept related with Spring Framework.While creating the framework of any project spring may perform a vital role,and here dependency injection come in pitcher.

Actually,Suppose in java you created two different classes as class A and class B, and whatever the function are available in class B you want to use in class A, So at that time dependency injection can be used. where you can crate object of one class in other,in the same way you can inject an entire class in another class to make it accessible. by this way dependency can be overcome.

DEPENDENCY INJECTION IS SIMPLY GLUING TWO CLASSES AND AT THE SAME TIME KEEPING THEM SEPARATE.




Dependency Injection is a type of implementation of the " Inversion of Control " principle on which is based Frameworks building.

Frameworks as stated in "Design Pattern" of GoF are classes that implement the main control flow logic raising the developer to do that, in this way Frameworks realize the inversion of control principle.

A way to implement as a technique, and not as class hierarchy, this IoC principle it is just Dependency Injection.

DI consists mainly into delegate the mapping of classes instances and type reference to that instances, to an external "entity": an object, static class, component, framework, etc...

Classes instances are the " dependencies ", the external binding of the calling component with the class instance through the reference it is the " injection ".

Obviously you can implement this technique in many way as you want from OOP point of view, see for example constructor injection , setter injection , interface injection .

Delegating a third party to carry out the task of match a ref to an object it is very useful when you want to completely separate a component that needs some services from the same services implementation.

In this way, when designing components, you can focus exclusively on their architecture and their specific logic, trusting on interfaces for collaborating with other objects without worry about any type of implementation changes of objects/services used, also if the same object you are using will be totally replaced (obviously respecting the interface).




Представим себе, что вы хотите отправиться на рыбалку:

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

  • При инъекции зависимостей кто-то другой заботится обо всех приготовлениях и предоставляет необходимое вам оборудование. Вы получите («введете») лодку, удочку и приманку - все готово к использованию.




Links