Статьи

Инъекция зависимости – ручной способ

Внедрение зависимостей — это метод, который отделяет поведение от разрешения зависимостей. Проще говоря, это позволяет разработчику определять классы с определенной функциональностью, которая зависит от различных соавторов, без необходимости определять, как будет получена ссылка на этих соавторов. Таким образом достигается разделение между различными компонентами и в целом вводится более чистый код. Более конкретно, вместо жесткого кодирования зависимостей компонент просто перечисляет необходимые службы, а во время выполнения внешний независимый компонент будет предоставлять доступ к этим службам. Мы не должны забывать, что внедрение зависимостей — это просто особая форма инверсии управления, когда обращаемая проблема — это процесс получения необходимой зависимости. Справочная статья для вышеупомянутых методов — «Обращение контейнеров управления и шаблон внедрения зависимостей» Мартина Фаулера .

Для внедрения зависимостей появился ряд платформ, наиболее известными из которых являются Spring и Guice (см. Статьи, связанные с JavaCodeGeeks Spring ). Тем не менее, использование целого каркаса для небольших проектов, несомненно, является излишним. Один из наших партнеров по JCG , автор блога «Death By Code» , написал небольшое введение о том, как обрабатывать внедрение зависимостей вручную . Посмотрим, что он скажет …

В другом посте «Мне действительно нужен синглтон?» Я написал о проблемах, представленных шаблоном проектирования Singleton. Когда к единственному уникальному экземпляру обращаются через метод getInstance (), Singleton действует как скрытая глобальная переменная и вводит жесткую связь и нежелательные зависимости. Я получил два немедленных вопроса от моих читателей:

Должен ли Singleton использоваться только с Dependency Injection Framework?
Если доступ к Singleton через getInstance () создает тесную связь, создание экземпляра любого другого класса через new () также вызывает тесную связь. Итак, как должен быть создан объект, сохраняющий слабую связь?

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

В любом приложении Java мы неоднократно сталкиваемся с двумя событиями:

  • Создание объекта
  • Взаимодействие между объектами — бизнес-логика

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
class MyClass {
 
 private A a; //A is an interface
 private B b; //B is an interface
 
 //Object creation within constructor
 MyClass(A a, B b) { 
    a = new AImpl(); //AImpl is the concrete impl of A
    b = new BImpl(); //BImpl is the concrete impl of B
 }
 
 //Application logic lies within this method
 public void doSomething() {
    //Do A specific thing
    //Do B specific thing
    C c = new CImpl(); //Object creation within the method.
    //Do C specific thing
 }
}

Проблемы с этим классом:

  1. Он не смог отделить создание объекта от бизнес-логики, что привело к тесной связи.
  2. Здесь было выполнено «программирование для реализации», а не для взаимодействия. Завтра, если требуются разные реализации A, B или C, код внутри класса должен быть изменен.
  3. Тестирование MyClass потребует проверки A, B, C в первую очередь.

Позвольте мне попытаться решить проблему:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class MyClass {
 
 private A a;
 private B b;
 private C c;
 
 MyClass(A a, B b, C c) {
    //Only Assignment
    this.a = a;
    this.b = b;
    this.c = c;
 }
 
 //Application Logic
 public void doSomething() {
    //Do A specific thing
    //Do B specific thing
    //Do C specific thing
 }
}
 
//The Factory
class MyFactory {
 public MyClass createMyClass() {
    return new MyClass(new AImpl(), new BImpl(), new CImpl());
 }
}
 
class Main {
 public static void main(String args[]) {
    MyClass mc = new MyFactory().createMyClass();
    mc.doSomething();
 }
}

Что было достигнуто здесь:

1. Конструктор не имеет нового ():
Объекты не создаются в конструкторе MyClass. Конструктор просто используется для назначения полей (A, B, C). Здесь конструктор запрашивает зависимости как параметры, но не создает их (и это самое простое определение внедрения зависимости). Тем не менее, простые объекты Collection, такие как ArrayList, HashMap, OR value / leaf, такие как Person / Employee (т. Е. Объекты в приложении, которые, в свою очередь, НЕ создают другие объекты), МОГУТ быть созданы внутри Конструктора. Конструктор не должен использоваться для каких-либо других операций, таких как ввод-вывод, создание потоков и т. Д.

Как правило большого пальца, любой объект должен содержать ссылки ТОЛЬКО на другие объекты, в которых он нуждается непосредственно для выполнения своей работы (это называется Законом Деметры). Например, если MyClass нужен какой-то другой класс с именем X, конструктор MyClass должен напрямую запросить X. Он не должен запрашивать какую-либо другую фабрику F, которая может возвратить экземпляр X. Нарушение «Закона Деметры» приведет к нежелательной зависимости между MyClass и F. Итак, если вы обнаружите более одного оператора Dot (.), будьте осторожны — там происходит что-то нелегальное.

2. Фабрика (MyFactory) занимается созданием объектов и их подключением:
Все новые операторы (90% -99%) должны принадлежать фабрике. Он должен позаботиться о создании всего графа объектов для приложения, а также о связывании (связывании) различных объектов на основе их объявленных зависимостей (например, MyClass требует A, B, C и т. Д.). Он не должен содержать ничего более — никакой другой логики (без ввода-вывода, создания потоков и т. Д.).

Завтра, если C начнет зависеть от чего-то еще, называемого D, это повлияет только на C и фабрику, а не на весь граф объекта (C должен будет ввести перегруженный конструктор, а фабрика должна будет включить экземпляр объекта плюс изменения, связанные с проводкой объекта) ,

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

3. Создание объекта отделено от бизнес-логики:
MyClass теперь является держателем бизнес-логики. У него нет новых (). Даже у него нет никаких знаний о конкретных реализациях, которые он использует для бизнес-логики (то есть он знает об A, но не об AImpl — «программа для интерфейса, а не для реализации»).

Вы, должно быть, начали думать, что я начал эту дискуссию с контекста Синглтона. Как ручное внедрение зависимости заботится о Синглтоне? Как он создает Singleton (без тесной связи, скрытой зависимости и т. Д.) И получает к нему доступ при необходимости? Удивительно, но у нас уже есть три синглтона в нашем примере — AImpl, BImpl, CImpl. Если фабрика позаботится о создании только одного экземпляра Класса (вызывая new () только один раз), это Singleton. Не так ли? Затем фабрика может передать этот уникальный экземпляр в виде зависимостей всем другим объектам, которые в этом нуждаются.

4. Итак, где мы?
MyClass, владельцу бизнес-логики нужны A, B и C для своего бизнеса. Он не создает их, а запрашивает их (зависимости). Фабрика (MyFactory) создает эти зависимости и связывает их с MyClass. Но кто создает фабрику? Конечно, основной метод (запуск приложения :-)). Позвольте мне повторить историю еще раз: метод main сначала создает фабрику, а фабрика, в свою очередь, создает граф объектов, каждый объект объявляет их зависимости, и, наконец, сам метод main запускает цикл — запускайте приложение, вызывая doSomething () из MyClass т.е. объекты начинают разговаривать друг с другом, занимаясь обычным делом.

Позвольте мне повторить это еще раз: создайте фабрику, создайте приложение, используя фабрику, а затем запустите приложение! Для крупномасштабного приложения то же самое может быть достигнуто с помощью инфраструктуры Dependency Injection, такой как Spring, Google Guice и т. Д. Конечно, кроме того, у них будет множество других преимуществ. Но для небольших и средних приложений внедрение зависимостей может быть выполнено вручную, что делает приложение слабо связанным, более легким в обслуживании и, конечно же, удобным для модульных тестов.

Статьи по Теме :