Демистифицируйте Dependency Injection и посмотрите на его действие с помощью этого короткого поступления

1656638048 demistificzirujte dependency injection i posmotrite na ego dejstvie s pomoshhyu

от Sankalp Bhatia

SEN9zD1pGC78YEzPQDK7LP6TOuB3MzbqXLe7
Фото Фабьена Бутацци на Unsplash

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

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

Что такое инъекция зависимости?

Прежде всего: что это? Инъекция зависимостей, как и многое другое жаргона разработки программного обеспечения, является модным термином для достаточно простого понятия.

Наиболее полезным для меня является определение:

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

Это оно. Предоставление (внедрение) зависимостей для класса. Просто так? Действительно.

Теперь есть три способа предоставления класса его зависимостей в Java. Все они достигают… (кашляет) Инъекции зависимости.

Они есть:

  • Конструкторы
  • Сетеры
  • Прямая настройка публичных полей

Давайте посмотрим на действие Dependency Injection

У меня есть класс приложения MyMessagePublisher, который зависит от определенного класса EmailService.

Введение зависимостей с помощью конструктора:

public class MyMessagePublisher {    private EmailService emailService = null;        public MyMessagePublisher(EmailService emailService){        this.emailService = emailService;    }}

Видите, что там делает конструктор? Он сообщает MyMessagePublisher использовать предоставленный им EmailService. Класс, создающий экземпляр MyMessagePublisher, должен предоставлять (вставлять) экземпляр EmailService (с помощью конструктора), который будет использоваться MyMessagePublisher. Что-то вроде этого:

EmailService emailService = new EmailService();MyMessagePublisher myMessagePublisher =                            new MyMessagePublisher(emailService);

Браво, Конструктор!

Инъекция зависимостей с помощью сеттера:

public class MyMessagePublisher {    private EmailService emailService = null;    public MyMessagePublisher() {    }    public setEmailService(EmailService emailService) {        this.emailService = emailService;    }}

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

MyMessagePublisher myMessagePublisher = new MyMessagePublisher();myMessagePublisher.setEmailService(new EmailService());

Браво, сеттер!

Инъекция зависимостей путем прямой настройки открытых полей

Никогда не делайте это!! Если вы зашли так далеко, читая статью, я думаю, вы знаете, что такой подход является злом.

Преимущества применения зависимостей

Я начну этот раздел с объяснения того, что мы теряем, если не используем DI.

Рассмотрим этот код. Я определил классы EmailService и MyMessage Publisher. MyMessagePublisher сам создает экземпляр объекта EmailService вместо использования методов DI, упомянутых выше.

public class EmailService {        public void sendEmail(String message, String receiver){        System.out.println("Email sent to " + receiver);    }}
public class MyMessagePublisher {    private EmailService emailService = new EmailService();    public void processMessages(String message, String receiver){        this.emailService.sendEmail(message, receiver);    }}

Приведенный выше код имеет некоторые ограничения:

  1. Если логика инициализации EmailService изменится (для инициализации требуется параметр конструктора), нам нужно будет внести изменения в класс MyMessagePublisher вместе со всем остальным кодовой базой, где мы используем EmailService без DI.
  2. Имеет плотное сцепление. Скажем, мы хотим отказаться от отправки электронных писем и вместо этого начать отправлять SMS. Тогда нам придется написать новый класс издателя.
  3. Этот код тестировать нельзя. При модульном тестировании класса MyMessagePublisher мы будем отправлять электронные письма всем.

Это решение, которое я предлагаю:

public class EmailService implements MessageService {    @Override    public void sendMessage(String message, String receiver) {        //logic to send email    }}
public class SMSService implements MessageService {    @Override    public void sendMessage(String message, String receiver) {        //logic to send SMS    }
public interface MessageService {    void sendMessage(String message, String receiver);}
public class MyMessagePublisher {    private MessageService messageService;        public MyMessagePublisher(MessageService messageService){        this.messageService = messageService;    }        @Override    public void processMessages(String msg, String rec){        this.service.sendMessage(msg, rec);    }}

Эта реализация преодолевает все три ограничения, упомянутые выше.

  • Логика инициализации EmailService (или MessageService) переходит к модулю, инициализирующему MyMessagePublisher
  • Мы можем перейти к другой реализации MessageService, отправляющей SMS без изменения кода в MyMessagePublisher
  • Код можно проверить. Мы можем использовать фиктивную реализацию MessageService при модульном тестировании MyMessagePublisher

Сладкий. Мы достигли DI с помощью конструкторов наших классов. Легко верно? Но и здесь есть некоторые недостатки.

Проблемы с Vanilla Dependency Injection

Когда это становится проблемой? Когда код растет.

Итак, какие альтернативы? Контейнеры инъекции зависимостей (Весна, Гайс, Кинжал и так далее).

Попробуем более подробно ответить на поставленные выше вопросы.

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

public class BookingService {    private SlotsManager slotsManager;    private MyMessagePublisher myMessagePublisher; //Looks familiar?}
public class AllInOneApp  {    BookingService bookingService; // Class above    RechargeService rechargeService;    MoneyTransferService moneyTransferService;    ShoppingService shoppingService;}

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

public static void main(String[] args) {
AllInOneApp allInOneApp = new AllInOneApp(                new BookingService(new SlotsManager(),                                   new MyMessagePublisher(                                              new EmailService())),                 new RechargeService(...),                 new MoneyTransferService(..),                new ShoppingService(...));
}

Это выглядит беспорядочно. Можем ли мы определить проблемы? Несколько из них:

  • Класс, инициализирующий AllInOneApp, также должен знать логику для создания всех классов зависимостей. Это громоздко при написании кода в любом проекте приличного размера. Управлять всеми этими экземплярами самостоятельно – это не то, что мы хотим делать при написании кода для бизнеса.
  • Хотя я видел, как люди предпочитают такой тип DI, я лично считаю, что этот код менее читаем по сравнению с этим:
AllInOneApp myApp = SomeDIContainer.getInstance(AllInOneApp.class);

Если все компоненты используют DI, то в системе какой-то класс или фабрика должны знать, что внедрить во все эти компоненты (AllInOneApp, BookingService, MessageService и т.д.).

Вот где появляются контейнеры Dependency Injection. Его называют контейнером, а не фабрикой, потому что контейнер частенько берет на себя больше ответственности, чем просто создание экземпляров объектов и внедрение зависимостей.

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

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

Спасибо за чтение 🙂

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *