Каков принцип инверсии зависимостей и почему это важно?

Что такое принцип инверсии зависимостей и почему это важно?

13 ответов


проверьте этот документ:Принцип Инверсии Зависимостей.

Он как бы говорит:

  • модули высокого уровня не должны зависеть от низкоуровневых модулей. И то и другое должно зависеть от абстракций.
  • абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

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

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

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


книги Agile Software Development, принципы, шаблоны и практики и Agile принципы, шаблоны и практики в C# являются лучшими ресурсами для полного понимания первоначальных целей и мотиваций, лежащих в основе принципа инверсии зависимостей. Статья "принцип инверсии зависимостей" также является хорошим ресурсом, но из-за того, что это сокращенная версия проекта, которая в конечном итоге пробилась в ранее упомянутые книги, она оставляет некоторые важные обсуждение концепции владения пакетом и интерфейсом, которые являются ключевыми для отличия этого принципа от более общих рекомендаций "программа к интерфейсу, а не реализация", найденных в рамках шаблонов дизайна книги (Gamma, et. Эл.)

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

Это достигается путем разработки компонентов, внешние зависимости которых выражаются в терминах интерфейса, для которого реализация должна быть предоставлена потребителем компонента. Другими словами, определенные интерфейсы выражают то, что необходимо компоненту, а не то, как вы используете компонент (например, "INeedSomething", а не "IDoSomething").

принцип инверсии зависимостей не относится к простой практике абстрагирования зависимостей через использование интерфейсов (например, MyService → [ILogger ⇐ Logger]). Хотя это отделяет компонент от конкретной детали реализации зависимости, он не инвертирует отношения между потребителем и зависимостью (например, [MyService → IMyServiceLogger] ⇐ Logger.

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

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


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

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

принцип инверсии зависимостей гласит, что:

  • модули высокого уровня не должны зависеть от модулей низкого уровня. И то и другое должно зависеть от абстракций.
  • абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

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


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

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

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

таким образом, создание отдельной абстракции на том же уровне компонента A, который зависит от компонента B (который не зависит от A), может быть сделано только в том случае, если компонент A действительно будет полезен для повторного использования в разных приложениях или контекстах. Если это не так, то применение DIP будет плохим дизайном.


в основном он говорит:

класс должен зависеть от абстракций (e.G интерфейс, абстрактные классы), а не конкретные детали (реализации).


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

традиционная слоистая архитектура

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

http://xurxodev.com/content/images/2016/02/Traditional-Layered.png

вы должны понимать слой, пакет или библиотеку. Посмотрим, каков будет код.

у нас будет библиотека или пакет для слоя доступа к данным.

// DataAccessLayer.dll
public class ProductDAO {

}

и другая бизнес-логика уровня библиотеки или пакета, которая зависит от уровня доступа к данным.

// BusinessLogicLayer.dll
using DataAccessLayer;
public class ProductBO { 
    private ProductDAO productDAO;
}

многоуровневая архитектура с зависимостью инверсия

инверсия зависимостей указывает на следующее:

высокоуровневые модули не должны зависеть от низкоуровневых модулей. Оба должны зависеть от абстракций.

абстракции не должны зависеть от деталей. Детали должны зависеть от абстракций.

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

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

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

у нас будет библиотека или пакет для уровня доступа к данным, которые определяют абстракцию.

// DataAccessLayer.dll
public interface IProductDAO
public class ProductDAO : IProductDAO{

}

и другая бизнес-логика уровня библиотеки или пакета, которая зависит от уровня доступа к данным.

// BusinessLogicLayer.dll
using DataAccessLayer;
public class ProductBO { 
    private IProductDAO productDAO;
}

хотя мы зависим от зависимости абстракции между бизнесом и доступом к данным, остается тот же.

http://xurxodev.com/content/images/2016/02/Traditional-Layered.png

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

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

// Domain.dll
public interface IProductRepository;

using DataAccessLayer;
public class ProductBO { 
    private IProductRepository productRepository;
}

после того, как уровень персистентности зависит от домена, переход к инвертированию теперь, если определена зависимость.

// Persistence.dll
public class ProductDAO : IProductRepository{

}

http://xurxodev.com/content/images/2016/02/Dependency-Inversion-Layers.png

углубление принципа

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

но почему мы инвертируем a зависимость? Какова главная цель помимо конкретных примеров?

такие обычно позволяет наиболее стабильным вещам, которые не зависят от менее стабильных вещей, меняться чаще.

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

но там не только этот пример репозитория. Существует много сценариев, в которых применяется этот принцип, и существуют архитектуры, основанные на этом принципе.

архитектура

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

Очистить Архитектуры

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

Гексагональной Архитектуры

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


гораздо более ясный способ заявить принцип инверсии зависимостей:

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

И. Е., вместо того, чтобы реализовать свой класс Logic как обычно делают люди:

class Dependency { ... }
class Logic {
    private Dependency dep;
    int doSomething() {
        // Business logic using dep here
    }
}

вы должны сделать что-то вроде:

class Dependency { ... }
interface Data { ... }
class DataFromDependency implements Data {
    private Dependency dep;
    ...
}
class Logic {
    int doSomething(Data data) {
        // compute something with data
    }
}

Data и DataFromDependency должен жить в том же модуле, что и Logic, а не Dependency.

почему это?

  1. два модуля бизнес-логики теперь разделены. Когда Dependency изменения, вам не нужно менять Logic.
  2. понимая, что Logic does-гораздо более простая задача: она работает только на том, что выглядит как ADT.
  3. Logic теперь можно более легко протестировать. Теперь вы можете непосредственно создать экземпляр Data с поддельными данными и передать его в. Нет необходимости насмешки или сложные леса теста.

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

пример псевдокода с использованием традиционного поиска:

class Service {
    Database database;
    init() {
        database = FrameworkSingleton.getService("database");
    }
}

аналогичный код с использованием IoC:

class Service {
    Database database;
    init(database) {
        this.database = database;
    }
}

преимущества МОК являются:

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

хорошие ответы и хорошие примеры уже даны другими здесь.

причина DIP важно потому что оно обеспечивает ОО-принцип "свободно соединенный дизайн".

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

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


инверсия контейнеров управления и шаблон инъекции зависимостей Мартин Фаулер тоже хорошо читает. Я нашел Головы Первый Дизайн Шаблоны удивительная книга для моего первого набега на изучение Ди и других моделей.


точка инверсии зависимостей-сделать многоразовое программное обеспечение.

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

наиболее часто это достигается путем инверсии контейнера control (IoC), такого как Spring в Java. В этой модели свойства объектов настраиваются с помощью конфигурации XML вместо объектов, выходящих и найти свою зависимость.

представьте себе, это псевдокод...

public class MyClass
{
  public Service myService = ServiceLocator.service;
}

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

public class MyClass
{
  public IService myService;
}

теперь MyClass полагается на один интерфейс, интерфейс IService. Мы бы позволили контейнеру IoC фактически установить значение этой переменной.

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

еще лучше, вам не нужно перетаскивать зависимости MyService и зависимости этих зависимостей, а также... ну, вы поняли.


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

  • представьте себе систему (webapp) с управлением сотрудниками и контактами (два экрана).
  • они не совсем связаны, поэтому вы хотите, чтобы каждый из них был в своем собственном модуле / папке

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

узел.пример Яш

// main.js
import express from 'express'

// two modules, each having many exports
import { api as contactsApi, navigation as cNav } from './contacts/'
import { api as employeesApi, navigation as eNav } from './employees/'

const api = express()
const navigation = {
  ...cNav,
  ...eNav
}

api.use('contacts', contactsApi)
api.use('employees', employeesApi)

// do something with navigation, possibly do some other setup

обратите внимание бывают случаи (простые), когда это совершенно нормально.


поэтому со временем он дойдет до точки, когда добавлять новые модули не так тривиально. Вы должны помнить, чтобы зарегистрировать api, навигация, может быть!--16-->разрешения, а это главное.js становится все больше и больше.

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

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

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

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


В дополнение к другим ответы....

позвольте мне сначала привести пример..

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

этот реализация находится в JAVA

FactoryClass с заводским методом. Генератор Пищи

public class FoodGenerator {
    Food food;
    public Food getFood(String name){
        if(name.equals("fish")){
            food =  new Fish();
        }else if(name.equals("chicken")){
            food =  new Chicken();
        }else food = null;

        return food;
    }
}


Абстрактный / Класс Интерфейса

public abstract class Food {

    //None of the child class will override this method to ensure quality...
    public void quality(){
        String fresh = "This is a fresh " + getName();
        String tasty = "This is a tasty " + getName();
        System.out.println(fresh);
        System.out.println(tasty);
    }
    public abstract String getName();
}


курица реализует пищу (конкретный класс)

public class Chicken extends Food {
    /*All the food types are required to be fresh and tasty so
     * They won't be overriding the super class method "property()"*/

    public String getName(){
        return "Chicken";
    }
}


рыба реализует пищу (бетон Класс)

public class Fish extends Food {
    /*All the food types are required to be fresh and tasty so
     * They won't be overriding the super class method "property()"*/

    public String getName(){
        return "Fish";
    }
}


Наконец

Отель

public class Hotel {

    public static void main(String args[]){
        //Using a Factory class....
        FoodGenerator foodGenerator = new FoodGenerator();
        //A factory method to instantiate the foods...
        Food food = foodGenerator.getFood("chicken");
        food.quality();
    }
}

как вы могли видеть, отель не знает, является ли это объектом курицы или объектами рыбы. Он знает только, что он является пищевым объектом i.E отель зависит от класса питания.

Также Вы заметите, что класс рыбы и курицы реализует класс питания и не связан с отелем напрямую. Я. E курица и рыба также зависят от пищи Класс.

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

Это называется инверсией зависимостей.