Рубрики
Без рубрики

О шаблонах проектирования: Внедрение зависимостей

Что такое внедрение зависимостей? Внедрение зависимостей (DI) – это очень простая концепция, которая направлена на.. Внедрение зависимостей (DI) – это очень простая концепция, которая направлена на..

Что такое внедрение зависимостей?

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

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

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

Давайте проясним все это на примере!

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

Давайте начнем без внедрения зависимостей.

Как вы можете видеть на диаграмме, Метеорологическая служба полагается на Термометр , , который может быть сконфигурирован с помощью TemperatureUnit . Отказ от использования внедрения зависимостей приведет к тому, что код создаст новый экземпляр Термометр на службе, и Термометр настройка/| Единицы измерения температуры для использования:

public class Thermometer {
  private final TemperatureUnit unit;

  public Thermometer() {
    this.unit = TemperatureUnit.CELSIUS;
  }
}

public class WeatherService implements WeatherContract {

  private final Thermometer thermometer;

  // This constructor is not using dependency injection
  public WeatherService() {
    this.thermometer = new Thermometer();
  }
}

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

public Thermometer(boolean useCelsius) {
  if (useCelsius) {
    this.unit = TemperatureUnit.CELSIUS;
  } else {
    this.unit = TemperatureUnit.FAHRENHEIT;
  }
}

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

public WeatherService(boolean useRealDevice, 
                      boolean useCelsius,
                      String apiKey) {
  if (useRealDevice) {
    this.thermometer = new Thermometer(useCelsius);
  } else {
    this.thermometer = new ThermometerWebService(useCelsius, apiKey);
  }
}

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

public static void main(String[] args) {
  // Not using dependency injection
  WeatherContract weather = new WeatherService(true, true, null);
}

Даже если он прост в использовании, наша текущая версия Даже если он прост в использовании, наша текущая версия

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

В результате любое изменение любого Реализация Thermometer может потребовать изменений в конструкторах WeatherService . Реализация Thermometer может потребовать изменений в конструкторах WeatherService

Улучшит ли внедрение зависимостей мой проект?

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

Инверсия управления представлена на этой диаграмме тем фактом, что наша реализация Weather Service связана с ThermometerContract , а не с какой-либо из ее реализаций. Это не более чем это.

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

public class WeatherService implements WeatherContract {
  // We now use the Interface   
  private final ThermometerContract thermometer;

  // New constructor using dependency injection    
  public WeatherService(ThermometerContract thermometer) {
    this.thermometer = thermometer;
  }
}

В результате инициализация Службы погоды для обоих конструкторов будет выглядеть следующим образом:

public static void main(String[] args) {
  // Using dependency injection
  TemperatureUnit celsius = TemperatureUnit.CELSIUS;
  ThermometerContract thermometer = new Thermometer(celsius);
  WeatherContract weather = new WeatherService(thermometer);
}

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

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

Это все, что нужно для внедрения зависимостей?

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

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

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

Давайте рассмотрим, что у нас есть две реализации со следующими конструкторами:

public class WeatherService implements WeatherContract {
  private final ThermometerContract thermometer;

  @Inject
  public WeatherService(ThermometerContract thermometer) {
    this.thermometer = thermometer;
  }
}

public class Thermometer implements ThermometerContract {
  private final TemperatureUnit unit;

  @Inject
  public Thermometer(@Named(WeatherModule.TEMPERATURE_UNIT) 
                     TemperatureUnit unit) {
    this.unit = unit;
  }
}

Модуль injection должен быть сконфигурирован для привязки всех необходимых интерфейсов к данной реализации. Он также должен иметь возможность вводить любой объект без определенного интерфейса, например enumerate TemperatureUnit . Затем инъекция будет привязана к определенному имени, в данном случае “ temp_unit”

public class WeatherModule extends AbstractModule {
  public static final String TEMPERATURE_UNIT = "temp_unit";

  @Override
  protected void configure() {
    // Named input configuration bindings
    bind(TemperatureUnit.class)
      .annotatedWith(Names.named(TEMPERATURE_UNIT))
      .toInstance(TemperatureUnit.CELSIUS);

    // Interface - Implementation bindings
    bind(ThermometerContract.class).to(Thermometer.class);
    bind(WeatherContract.class).to(WeatherService.class);
  }
}

В конечном счете, модуль можно использовать следующим образом, здесь создается экземпляр Погодного контракта/| .

public static void main(String[] args) {
  // Creating the injection module configured above.
  Injector injector = Guice.createInjector(new WeatherModule());

  // We ask for the injection of a WeatherContract, 
  // which will create an instance of ThermometerContract
  // with the named TemperatureUnit under the hood.
  WeatherContract weather = injector.getInstance(WeatherContract.class);
}

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

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

Покажи мне несколько тестов!

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

Как было сказано выше, создание Weather Service запроса на Контракт с термометром позволяет нам использовать любую реализацию, которую мы хотим. Следовательно, мы можем отправить mock в конструкторе, а затем управлять его поведением извне.

public void testTemperatureStatus() {
  ThermometerContract thermometer = Mockito.mock(ThermometerContract.class);
  Mockito.doReturn(TemperatureUnit.CELSIUS).when(thermometer).getUnit();
  WeatherContract weather = new WeatherService(thermometer);

  Mockito.doReturn(-50f).when(thermometer).getTemperature();
  assertEquals(
    TemperatureStatus.COLD,
    weather.getTemperatureStatus()
  );

  Mockito.doReturn(10f).when(thermometer).getTemperature();
  assertEquals(
    TemperatureStatus.MODERATE,
    weather.getTemperatureStatus()
  );
}

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

Вывод

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

Внедрение зависимостей обеспечивает множество существенных преимуществ, таких как:

  • Развязка кода : используйте контракты и игнорируйте особенности реализации.
  • Улучшенная тестируемость : Писать модульные тесты становится почти удовольствием.
  • Конфигурируемость : вы можете более легко поменять местами введенные экземпляры.

Вы можете найти полный пример кода в моем репозитории руководств по дизайну на GitHub

Оригинал: “https://dev.to/aveuiller/about-design-patterns-dependency-injection-2hah”