Pull to refresh

Dagger 2 для начинающих Android разработчиков. Внедрение зависимостей. Часть 1

Reading time 5 min
Views 40K
Original author: Hari Vignesh Jayapalan
Данная статья является второй частью серии статей, предназначенных, по словам автора, для тех, кто не может разобраться с внедрением зависимостей и фреймворком Dagger 2, либо только собирается это сделать. Оригинал написан 25 ноября 2017 года. Изображения и GIF — из оригинала. Перевод вольный.

image

Это вторая статья цикла «Dagger 2 для начинающих Android разработчиков.». Если вы не читали первую, то вам сюда.

Серия статей



Ранее в цикле статей


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

Что такое внедрение зависимостей (dependency injection, DI)


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

Перед тем, как разбирать внедрение зависимостей, нам нужно понять как избежать ловушки, в которой мы будем окружены зависимостями. Проблема сильных связей (hard dependency) как Белые Ходоки (White Walkers). Мы не должны присоединиться к их армии, напротив, нужно найти путь, чтобы победить их.

media


Стратегия решения проблемы сильных связей (hard dependency) или проблемы Белых Ходоков


Нам нужен четкий план для решения или предотвращения проблемы сильных связей. Эта стратегия или план называется внедрение зависимостей (dependency injection, DI). Другими словами, чтобы убить Белого Ходока вам нужно сжечь его или использовать оружие из драконьего стекла. Аналогично, чтобы избежать сильных связей, необходимо использовать внедрение зависимостей.

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

Метод внедрения зависимостей


Внедрение зависимостей — это метод, при котором один объект предоставляет зависимости другого объекта. Зависимость (dependency) — это объект, который мы можем использовать (сервис). Внедрение (инъекция, injection) — это передача зависимости зависимому объекту (клиенту), который будет данной зависимостью пользоваться. Сервис — это часть состояния клиента. Передать сервис клиенту, вместо того, чтобы позволить клиенту создать или найти сервис — базовое требование шаблона проектирования «Внедрение зависимости» (DI).

Сравним это с Игрой Престолов. Серсея готовится к большой войне. Вместо того, чтобы сжигать все деньги в своем королевстве она пытается получить кредит у железного банка Браавоса. В этой ситуации деньги — это зависимость. Деньги нужны всем домам, чтобы вести войну. Таким образом внешний объект (железный банк) будет внедрять зависимость (деньги) зависимым объектам (домам).

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

Пример 1


Хватить говорить. Давайте разбирать код. Рассмотрим небольшой пример с двумя сценариями. В первом сценарии создадим несколько классов с сильными связями (hard dependencies), то есть без использования внедрения зависимостей. Затем, следуя шаблону «Внедрение зависимости», мы избавимся от сильных связей.

Сценарий 1. Без использования внедрения зависимостей


Проблема: Битва бастардов — Старки (Starks) и Болтоны (Boltons) готовятся к войне, чтобы захватить Север. Нужно их подготовить и вывести на войну.

media


Поскольку пример может включать много домов, создадим общий интерфейс House, включим в него методы prepareForWar() и reportForWar().

public interface House {
    void prepareForWar();
    void reportForWar();
}

Далее создадим классы для домов Starks и Boltons. Классы будут реализовать интерфейс House.

public class Starks implements House {
    @Override
    public void prepareForWar() {
        //что-то происходит
        System.out.println(this.getClass().getSimpleName()+" prepared for war");
    }
    @Override
    public void reportForWar() {
        //что-то происходит
        System.out.println(this.getClass().getSimpleName()+" reporting..");
    }
}

public class Boltons implements House {
    @Override
    public void prepareForWar() {
        //что-то происходит
        System.out.println(this.getClass().getSimpleName()+" prepared for war");
    }
    @Override
    public void reportForWar() {
        //что-то происходит
        System.out.println(this.getClass().getSimpleName()+" reporting..");
    }
}

Заметка: this.getClass().getSimpleName() просто возвращает имя класса

Далее нужно привести оба дома к войне. Создадим класс War и попросим оба дома подготовиться к войне и сообщить о ней.

public class War {
    private Starks starks;
    private Boltons boltons;

    public War(){
        starks = new Starks();
        boltons = new Boltons();

        starks.prepareForWar();
        starks.reportForWar();
        boltons.prepareForWar();
        boltons.reportForWar();
    }
}

Анализируем War


Рассмотрим конструктор класса War. Для работы ему необходимы два класса Starks и Boltons. Эти классы создаются внутри конструктора, готовятся к войне и сообщают о ней.

В хорошо спроектированном объектно-ориентированном приложении у каждого объекта минимальное количество обязанностей, объекты опираются на другие для выполнения большей части работы. В данном примере класс War зависит от Starks и Boltons. Это зависимости класса War. Без них War работать не будет. Перед тем, как класс начнет выполнять реальные функции, все его зависимости должны быть удовлетворены каким-либо образом. Зависимости класса War в этом примере удовлетворяются путем создания экземпляров классов в конструкторе.

Хотя вариант с созданием зависимостей внутри конструктора класса достаточно хорошо работает в небольших приложениях — он привносит недостатки по мере роста проекта.

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

Во-вторых, невозможно изолированно протестировать эти классы. Создание экземпляра War автоматически создает два других объекта, которые, в конечном итоге, будут тестироваться вместе с классом War. Это может стать проблемой, если один из объектов зависит от дорогого внешнего ресурса, например, Allies (союзники) или один из объектов сам зависит от множества других.

Как упоминалось ранее — борьба с сильными связями (hard dependencies) похожа на борьбу с Белыми Ходоками без надлежащего оружия.

Резюме


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

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

Также мы рассмотрели пример с сильными связями (hard dependencies), создавая сценарий битвы бастардов.

Что дальше?


В следующей статье мы реализуем класс War, используя шаблон «Внедрение зависимости» с традиционным подходом.
Tags:
Hubs:
+3
Comments 2
Comments Comments 2

Articles