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

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

Некоторым людям трудно понять или объяснить внедрение зависимостей — я был одним из них. Я думаю, это в основном из-за вводящего в заблуждение названия, которое вызывает неправильные ассоциации о том, что такое внедрение зависимостей. При «внедрении зависимостей» вы внедряете не зависимость, а объект. Вы также не удаляете зависимость, а вместо этого удаляете зависимость от объекта класса. Но вы также не внедряете объект класса, потому что, если внедрение зависимостей сделано правильно, класс остается неизвестным для клиента.

Допустим, у вас есть следующий код:

public class MyClient {
   MyClass myDependency = new MyClass();
}

MyClient и MyClass тесно связаны. Чтобы отделить его, вы можете заменить класс MyClass интерфейсом:

interface IMyClass {}
public class MyClass implements IMyClass {}
public class MyClient {
   IMyClass myDependency = new MyClass();
}

Однако у вас все еще есть конструктор MyClass в MyClient . Чтобы также отделить класс от этой зависимости, мы будем использовать внедрение зависимостей. В зависимости от того, как вы хотите внедрить объект (в данном случае объект MyClass), существует 3 различных вида внедрения зависимостей.

Внедрение конструктора

Зависимость передается в конструктор

public class Client {
   IMyClass myDependency;
   Client (IClass theDependency) {
      myDependency = theDependency;
   }
}

Часто это самый распространенный способ использования DI.

Преимущества:

  • самый элегантный способ реализации DI
  • если зависимость важна, вы ее не забудете, потому что она нужна конструктору

Недостаток:

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

Внедрение сеттера

С Setter Injection зависимость вводится с помощью метода установки:

public class Client {
   IMyClass myDependency;
   //...
   void setDependency(IMyClass theDependency) {
       myDependency = theDependency;
   }
}

Преимущество:

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

Недостатки

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

Внедрение интерфейса

В Interface Injection вы создаете интерфейс с методом внедрения зависимости. В принципе, как в Setter Injection, но спецификация перенесена в интерфейс:

interface MyClassInjected {
   void injectClass(IMyClass);
}
public class Client implements MyClassInjected {
   IMyClass myDependency;
   //...
   @Override
   void injectClass(IMyClass theDependency) {
       myDependency = theDependency;
   }
}

Преимущество:

  • Клиенту не нужно знать интерфейс класса, а только интерфейс для создания зависимости

Недостаток:

  • недостатки аналогичны недостаткам Setter Injection: вы можете забыть либо внедрить зависимость, либо случайно внедрить другую зависимость в разное время и, таким образом, перезаписать зависимость

Имейте в виду, однако, DI не золотая пуля. При использовании внедрения зависимостей существуют ограничения. В последней части я объясню вам, в каких случаях нельзя использовать DI:

  • Условный: если зависимость привязана к условию, вы не можете использовать DI, потому что вышеуказанные методы не могут узнать, выполняется условие или нет.
  • Момент времени: вам нужно знать конкретный момент времени вне клиента, когда нужно назначить зависимость, например. при инициализации или когда устанавливать Setter Injection. Если это не детерминировано, то использовать DI невозможно
  • Временные переменные. Если переменная является временной, ее значение определяется логикой программирования. Так что нет смысла вводить его извне

Надеюсь, моя статья немного прояснила для вас ситуацию. У вас есть что добавить?