Разница между внедрением зависимости и инверсией зависимости

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

Мне нужно понять это в PHP.

6 ответов

(Примечание: этот ответ не зависит от языка, хотя в этом вопросе конкретно упоминается PHP, но, будучи незнакомым с PHP, я не предоставил никаких примеров PHP).

Инъекция против Инверсии

  • Внедрение зависимостей - это метод инверсии управления для предоставления объектов ("зависимостей") в класс посредством шаблона проектирования внедрения зависимостей. Как правило, передача зависимостей через одно из следующих:

    • Конструктор
    • Публичная собственность или поле
    • Общественный сеттер
  • Принцип инверсии зависимостей (DIP) - это руководство по разработке программного обеспечения, которое сводится к двум рекомендациям по отсоединению класса от его конкретных зависимостей:

    1. "Модули высокого уровня не должны зависеть от модулей низкого уровня. Оба должны зависеть от абстракций.
    2. "Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

Внедрение зависимости

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

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

// Dependency Injection Example...

class Foo {
    // Constructor uses DI to obtain the Meow and Woof dependencies
    constructor(fred: Meow, barney: Woof) {
        this.fred = fred;
        this.barney = barney;
    }
}

В этом примере Meow а также Woof обе зависимости вводятся через Foo конструктор.

С другой стороны, Foo класс, который разработан без внедрения зависимостей, может просто создать Meow а также Woof самих экземпляров или, возможно, используют какой-то сервисный локатор / фабрику

// Example without Dependency Injection...

class Foo {
    constructor() {
        // a 'Meow' instance is created within the Foo constructor
        this.fred = new Meow();

        // a service locator gets a 'WoofFactory' which in-turn
        // is responsible for creating a 'Woof' instance.
        // This demonstrates IoC but not Dependency Injection.
        var factory = TheServiceLocator.GetWoofFactory();
        this.barney = factory.CreateWoof();
    }
}

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


Инверсия зависимостей

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

Примечание. Инверсия зависимости часто более явно выражена в языках программирования со статической типизацией, таких как C# или Java, поскольку эти языки обеспечивают строгую проверку типов для имен переменных. С другой стороны, Deverdency Inversion уже пассивно доступна в динамических языках, таких как Python или JavaScript, потому что переменные в этих языках не имеют каких-либо особых ограничений типов.

Рассмотрим сценарий на языке со статической типизацией, где классу требуется умение читать записи из базы данных приложения:

class Foo {
    reader: SqlRecordReader;

    constructor(sqlReader: SqlRecordReader) {
        this.reader = sqlReader;
    }

    doSomething() {
        var records = this.reader.readAll();
        // etc.
    }
}

В приведенном выше примере класса Foo имеет жесткую зависимость от SqlRecordReader, но единственное, что его действительно волнует, это то, что существует метод, называемый readAll() который возвращает несколько записей.

Рассмотрим ситуацию, когда запросы к базе данных SQL разделяются на отдельные микросервисы; Foo класс должен будет читать записи из службы удаления. Или, альтернативно, ситуация, когда Foo модульные тесты должны читать данные из хранилища в памяти или плоского файла.

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

Руководства по инверсии зависимостей предполагают, что SqlRecordReader следует заменить на абстракцию, которая обеспечивает только readAll() метод. то есть:

interface IRecordReader {
    Records[] getAll();
}

class Foo {
    reader: IRecordReader;

    constructor(reader: IRecordReader) {
        this.reader = reader;
    }
}

Согласно DIP IRecordReader это абстракция, и заставляет Foo зависит от IRecordReader вместо SqlRecordReader удовлетворяет требованиям DIP.


Почему рекомендации DIP полезны

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

Во многих случаях косвенность может сделать код проще в обслуживании (исправить ошибки, добавить улучшения), однако:

В этом последнем примере Foo может получить SqlRecordReader или, может быть, SoapRecordReader или, возможно, FileRecordReader или, может быть, даже для модульного тестирования MockRecordReader - Дело в том, что он не знает и не заботится о различных возможных реализациях IRecordReader - если, конечно, эти реализации соответствуют принципу замещения Лискова.

Кроме того, он избегает потенциально грязного сценария, когда разработчик, который спешит получить что-то работающее, может рассмотреть попытку "обмануть" принцип Лискова, унаследовав SoapRecordReader или же FileRecordReader из базового класса SqlRecordReader,

Хуже того, неопытный разработчик может даже изменить SqlRecordReader Сам по себе этот класс имеет логику не только для SQL, но и для конечных точек SOAP, Файловой системы и всего остального, что может понадобиться. (Подобные вещи случаются слишком часто в реальном мире - особенно в плохо поддерживаемом коде и почти всегда являются запахом кода.)

Смотрите эту статью здесь

Автор различает эти два в простых словах. Инъекция зависимости == "Дай мне это" и Инверсия зависимости == "Кто-то позаботится об этом для меня, так или иначе". В принципе инверсии зависимостей, модуль высокого уровня является владельцем абстракции. Таким образом, детализация (реализация абстракции) зависит от абстракции и, следовательно, от модуля высокого уровня. Зависимость инвертирована!.. Зависимость впрыска отличается. Абстракция не может быть сохранена модулем высокого уровня. Таким образом, абстракция, данная объекту более высокого уровня, может не ограничиваться потребностями модуля высокого уровня.

Инверсия зависимостей:

У вас есть модуль X более высокого уровня и абстракция Y, которая определяется X. Z реализует Y и передается X. Таким образом, Z зависит от X(через абстракцию Y, определенную X).

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

У вас есть модуль X более высокого уровня, которому нужны функциональные возможности A и B. Y - абстракция, которая содержит функциональные возможности A, B и C. Z реализует Y. Поскольку Z реализует Y и, следовательно, имеет функциональные возможности A и B, Z передается X. Теперь X зависит от Y.

Внедрение зависимостей - это способность объекта снабжать зависимостями другого объекта. простыми словами это означает, что что-то еще зависит от чего-то другого. Пример Класс A использует несколько функций класса B, теперь классу A необходимо создать экземпляр класса B, здесь используется DI.

МОК призван поменять местами различные обязанности, например, вам нужно работать дома, но вам нужно готовить, чтобы поесть. Теперь перечисленное приготовление еды дома вы можете заказать онлайн, и оно будет доступно для вашего порога, что означает, что вы можете сосредоточиться на своей работе. Здесь вы перевернули обязанности по приготовлению пищи в онлайн-реставрацию.

Принцип инверсии зависимостей (DIP) утверждает, что модули высокого уровня не должны зависеть от модулей низкого уровня; оба должны зависеть от абстракций. Абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

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

Здесь вы можете найти красивый пример PHP.
https://code.tutsplus.com/tutorials/dependency-injection-in-php--net-28146

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

      public interface ILog
{
    void WriteLog(string message);
}

public class Log : ILog
{
    public void WriteLog(string message)
    {
        // Implementation details...
    }
}

public class Test
{
    private readonly ILog _obj;

    public Test(ILog obj)
    {
        _obj = obj;
    }

    // Other members...
}

Внедрение зависимостей: механизм предоставления или «внедрения» этих зависимостей в класс (вручную или с использованием платформы). В некотором коде установки DI, например Program.cs в ASP.NET Core, пример реализации внедрения зависимостей:

      builder.Services.AddTransient<ILog, Log>();
Другие вопросы по тегам