dependency-injection - pattern - внедрение зависимостей swift




Почему используется инъекция зависимостей? (4)

Я пытаюсь понять инъекции зависимостей (DI), и еще раз я потерпел неудачу. Это просто кажется глупым. Мой код никогда не бывает беспорядок; Я почти не пишу виртуальных функций и интерфейсов (хотя я делаю это однажды в синей луне), и вся моя конфигурация магически сериализована в класс с использованием json.net (иногда с использованием сериализатора XML).

Я не совсем понимаю, какую проблему он решает. Это похоже на способ сказать: «Привет. Когда вы запускаете эту функцию, верните объект, который имеет этот тип, и использует эти параметры / данные».
Но ... зачем мне это использовать? Примечание. Мне никогда не нужно было использовать object , но я понимаю, для чего это нужно.

Каковы реальные ситуации при создании веб-сайта или настольного приложения, в котором можно было бы использовать DI? Я могу легко найти случаи, когда кто-то может захотеть использовать интерфейсы / виртуальные функции в игре, но это очень редко (достаточно редко, что я не могу вспомнить один экземпляр), чтобы использовать это в не-игровом коде.


Во-первых, я хочу объяснить предположение, которое я делаю для этого ответа. Это не всегда так, но довольно часто:

Интерфейсы являются прилагательными; классы - существительные.

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

Так, например, интерфейс может быть чем-то вроде IDisposable , IEnumerable или IPrintable . Класс является фактической реализацией одного или нескольких из этих интерфейсов: List или Map могут быть реализацией IEnumerable .

Чтобы понять суть вопроса: часто ваши классы зависят друг от друга. Например, у вас может быть класс Database который обращается к вашей базе данных (hah, surprise! ;-)), но вы также хотите, чтобы этот класс регистрировал доступ к базе данных. Предположим, у вас есть другой класс Logger , тогда у Database есть зависимость от Logger .

Все идет нормально.

Вы можете смоделировать эту зависимость в своем классе Database со следующей строкой:

var logger = new Logger();

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

И, конечно же, вы НЕ хотите менять весь свой код (между тем у вас есть gazillions) и заменить все строки

var logger = new Logger();

от:

var logger = new TcpLogger();

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

Очевидно, что неплохо представить интерфейс ICanLog (или аналогичный), который реализуется всеми различными регистраторами. Итак, шаг 1 в вашем коде состоит в том, что вы делаете:

ICanLog logger = new Logger();

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

ICanLog logger = LoggerFactory.Create();

Сама фабрика решает, какой регистратор создать. Ваш код больше не заботится, и если вы хотите изменить тип используемого регистратора, вы меняете его один раз : внутри фабрики.

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

ICanLog logger = TypeFactory.Create<ICanLog>();

Где-то этот TypeFactory нуждается в конфигурационных данных, которые фактический класс создает для экземпляра, когда запрашивается конкретный тип интерфейса, поэтому вам нужно сопоставление. Конечно, вы можете сделать это отображение внутри своего кода, но затем изменение типа означает перекомпиляцию. Но вы также можете поместить это сопоставление в XML-файл, например. Это позволяет вам изменить фактически используемый класс даже после времени компиляции (!), Что означает динамически, без перекомпиляции!

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

И теперь, когда вы немного заменяете имена, вы получаете простую реализацию Service Locator , которая является одним из двух шаблонов для Inversion of Control (поскольку вы инвертируете контроль над тем, кто решает, какой именно класс должен создать экземпляр).

В целом это уменьшает зависимости в вашем коде, но теперь весь ваш код имеет зависимость от центрального локатора с одним сервисом.

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

При внедрении зависимостей ваш класс Database теперь имеет конструктор, для которого требуется параметр типа ICanLog :

public Database(ICanLog logger) { ... }

Теперь ваша база данных всегда использует регистратор, но она больше не знает, откуда этот регистратор.

И здесь вступает в действие каркас DI: вы снова настраиваете свои сопоставления, а затем задаете свой фреймворк DI для создания вашего приложения для вас. Поскольку для класса Application требуется реализация ICanPersistData , вводится экземпляр Database , но для этого он должен сначала создать экземпляр регистратора, который настроен для ICanLog . И так далее ...

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

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

Но в принципе, все.

Надеюсь, это поможет.

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


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

IoC - это принцип, где DI - образец. Причина, по которой вам может понадобиться более одного регистратора, никогда не выполняется на самом деле, насколько мне известно, но фактическая причина в том, что вы действительно нуждаетесь в ней, всякий раз, когда вы что-то проверяете. Пример:

Моя особенность:

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

Вы можете проверить это следующим образом:

[Test]
public void ShouldUpdateTimeStamp
{
    // Arrange
    var formdata = { . . . }

    // System under Test
    var weasel = new OfferWeasel();

    // Act
    var offer = weasel.Create(formdata)

    // Assert
    offer.LastUpdated.Should().Be(new DateTime(2013,01,13,13,01,0,0));
}

Итак, где-то в OfferWeasel , он создает вам предложение Object следующим образом:

public class OfferWeasel
{
    public Offer Create(Formdata formdata)
    {
        var offer = new Offer();
        offer.LastUpdated = DateTime.Now;
        return offer;
    }
}

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

public interface IGotTheTime
{
    DateTime Now {get;}
}

public class CannedTime : IGotTheTime
{
    public DateTime Now {get; set;}
}

public class ActualTime : IGotTheTime
{
    public DateTime Now {get { return DateTime.Now; }}
}

public class OfferWeasel
{
    private readonly IGotTheTime _time;

    public OfferWeasel(IGotTheTime time)
    {
        _time = time;
    }

    public Offer Create(Formdata formdata)
    {
        var offer = new Offer();
        offer.LastUpdated = _time.Now;
        return offer;
    }
}

Интерфейс - это абстракция. Одна из них - это настоящая вещь, а другая - подделка времени, когда она необходима. Затем тест можно изменить следующим образом:

[Test]
public void ShouldUpdateTimeStamp
{
    // Arrange
    var date = new DateTime(2013, 01, 13, 13, 01, 0, 0);
    var formdata = { . . . }

    var time = new CannedTime { Now = date };

    // System under test
    var weasel= new OfferWeasel(time);

    // Act
    var offer = weasel.Create(formdata)

    // Assert
    offer.LastUpdated.Should().Be(date);
}

Таким образом, вы применили принцип «инверсии управления», введя зависимость (получив текущее время). Основная причина для этого - упростить единичное тестирование, есть другие способы сделать это. Например, интерфейс и класс здесь не нужны, поскольку в C # функции могут передаваться как переменные, поэтому вместо интерфейса вы можете использовать Func<DateTime> для достижения того же. Или, если вы используете динамический подход, вы просто передаете любой объект, который имеет эквивалентный метод ( утиная печать ), и вам совсем не нужен интерфейс.

Вам вряд ли понадобится больше одного регистратора. Тем не менее, инъекция зависимостей необходима для статически типизированного кода, такого как Java или C #.

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

Надеюсь, это помогло.


Я думаю, что много раз люди путаются о различии между инъекцией зависимостей и инфраструктурой инъекций зависимостей (или контейнером, как его часто называют).

Инъекционная инъекция - очень простая концепция. Вместо этого кода:

public class A {
  private B b;

  public A() {
    this.b = new B(); // A *depends on* B
  }

  public void DoSomeStuff() {
    // Do something with B here
  }
}

public static void Main(string[] args) {
  A a = new A();
  a.DoSomeStuff();
}

вы пишете такой код:

public class A {
  private B b;

  public A(B b) { // A now takes its dependencies as arguments
    this.b = b; // look ma, no "new"!
  }

  public void DoSomeStuff() {
    // Do something with B here
  }
}

public static void Main(string[] args) {
  B b = new B(); // B is constructed here instead
  A a = new A(b);
  a.DoSomeStuff();
}

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

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


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

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

class PaymentProcessor{

    private String type;

    public PaymentProcessor(String type){
        this.type = type;
    }

    public void authorize(){
        if (type.equals(Consts.PAYPAL)){
            // Do this;
        }
        else if(type.equals(Consts.OTHER_PROCESSOR)){
            // Do that;
        }
    }
}

Теперь представьте себе, что теперь вам нужно будет поддерживать весь этот код в одном классе, потому что он не отсоединен должным образом, вы можете себе представить, что для каждого нового процессора, который вы будете поддерживать, вам нужно создать новый if if switch case for каждый метод только усложняется, однако, используя Injection Dependency (или Inversion of Control), как его иногда называют, что означает, что тот, кто контролирует запуск программы, известен только во время выполнения, а не в сложности), вы могли бы достичь чего-то очень аккуратный и удобный.

class PaypalProcessor implements PaymentProcessor{

    public void authorize(){
        // Do PayPal authorization
    }
}

class OtherProcessor implements PaymentProcessor{

    public void authorize(){
        // Do other processor authorization
    }
}

class PaymentFactory{

    public static PaymentProcessor create(String type){

        switch(type){
            case Consts.PAYPAL;
                return new PaypalProcessor();

            case Consts.OTHER_PROCESSOR;
                return new OtherProcessor();
        }
    }
}

interface PaymentProcessor{
    void authorize();
}

** Код не компилируется, я знаю :)





dependency-injection