Ниже вы увидите вольный перевод статьи Miquel Beltran, опубликованной на
Medium 12 февраля 2016 года. Целью статьи является формирование понимания базового механизма работы Dagger 2.

Я обнаружил, что большинство руководств, объясняющих как работает Dagger, слишком сложны. Даже руководство Google/Square слишком тяжелое для понимания, если у вас нет четкого представления о том, как работает внедрение зависимостей.

Чтобы понять это, я создал очень простой Java-проект с несколькими классами, который показывает, как работает Dagger.

В этой статье я объясню основные составляющие Dagger. Статья нацелена на тех, кто не пользуется данной библиотекой, но планирует.

Структура проекта


apply plugin: 'java'

repositories {
  jcenter()
}

dependencies {
  testCompile 'junit:junit:4.12'
  compile 'com.google.dagger:dagger:2.0'
  compile 'com.google.dagger:dagger-compiler:2.0'
}

Это код файла build.gradle. Будет использоваться стандартный java плагин и JUnit для создания модульных тестов. Не забудьте добавить в зависимости библиотеку dagger-compiler (это была моя первая ошибка).

Покажите мне код


В этом примере будет два класса:

  • GameData: этот класс предоставляет данные, необходимые классу GameSession. В нашем случае — просто строка.
  • GameSession: этому классу требуется GameData. Мы будем внедрять зависимость с помощью Dagger, вместо передачи GameData параметром или создания экземпляра внутри GameSession.

Без внедрения зависимостей получилось бы что-то вроде следующего: класс GameData создается внутри GameSession. Некоторые разработчики согласятся с тем, что это плохая практика. Например, если требуется другой экземпляр GameData для тестирования, то создать его будет невозможно.

public class GameData {
    public final String hello = "Hello Dagger";
}

public class GameSession {
    public GameData data = new GameData();
}

Dagger позаботится за нас о внедрении экземпляра класса GameData в переменную класса GameSession. Необходимо лишь указать это с помощью аннотации @Inject.

public class GameData {
  public final String hello = "Hello Dagger";
}

import javax.inject.Inject;

public class GameSession {
    @Inject
    public GameData data;
}

Теперь нужно создать классы, которые определят, как будет реализовано внедрение зависимостей, это Component и Module.

  • Module определяет всех поставщиков инъекций. То есть определяет методы, которые вернут нам конкретные экземпляры для внедрения в зависимый класс. В данном случае нужно определить поставщика, который вернет нам GameData.
  • Component — это интерфейс, который Dagger будет использовать для генерации кода, чтобы внедрить зависимости за нас.

import dagger.Component;

@Component(modules = GameModule.class)
public interface GameComponent {
    void inject(GameSession obj);
}

import dagger.Module;
import dagger.Provides;

@Module
public class GameModule {
    @Provides
    GameData providesGameData() {
        return new GameData();
    }
}

  • GameModule содержит функцию с аннотацией @Provides, которая говорит Dagger, что это одна из тех функций, которая возвращает экземпляр GameData.
  • Необходимо также отметить класс GameModule аннотацией @Module.
  • Интерфейс GameComponent определяет функцию инъекции. Эта функция будет использоваться в классе GameSession и внедрит в него экземпляр класса GameData, который вернет функция GameModule.providesGameData().

В этом моменте заключена вся магия. Dagger поймет, что классу GameSession требуется класс GameData, что класс GameModule определяет, как будет получен экземпляр GameData; что необходимо воспользоваться интерфейсом GameComponent для начала инъекции, вызываемой из класса GameSession.

Использование Component


Следующий модульный тест показывает, как внедрить класс GameData в созданный экземпляр класса GameSession, используя сгенерированный класс типа Component.

import org.junit.Test;
import static org.junit.Assert.*;

public class GameSessionTest {
    @Test
    public void testGameSession() {
        GameSession session = new GameSession();
        DaggerGameComponent.create().inject(session);
        assertEquals("Hello Dagger", session.data.hello);
    }
}

  1. Класс DaggerGameComponent сгенерирован Dagger, включая функцию create().
  2. Этот класс реализует интерфейс GameComponent, в котором мы можем вызвать метод inject() и передать в него экземпляр GameSession.
  3. Метод inject() позаботится о том, чтобы внедрить все зависимости для GameSession.
  4. Наконец, мы можем увидеть, что значение поля data класса GameSession установлено.

Этот пример прост, в нём пропущено множество функциональных возможностей Dagger. Как я уже сказал, этот пример помог мне понять основы, и я надеюсь, что он поможет и вам.
Теперь, когда у вас есть общее представление о том, как работает Dagger 2, я рекомендую вернуться к исходной документации и попробовать разобрать пример с CoffeeMaker.

Комментарии (4)


  1. ScratchBoom
    26.11.2017 15:56

    сколько раз не пытался понять, чем даггер удобнее dependency injection через конструктор — так и не понял.


    1. tehreh1uneh Автор
      26.11.2017 16:12

      Я не такой хороший специалист в этой области, чтобы полноценно и внятно ответить на вопрос. Сейчас читаю ещё одну свежую серию статей на medium. Как найду время — опубликую перевод здесь. Автор статей разбирает основы, начиная с базовых идей DI. В этой серии статей можно будет найти ответ на вопрос, чем же хороша идея DI в сравнении с использованием связи через конструктор.



    1. andrikeev
      27.11.2017 08:54

      Инъекция через конструктор как раз является приоритетным способом при использовании Dagger. Но иногда это невозможно, например, когда созданием экземпляра объекта занимается фреймворк, и конструктор не должен содержать параметров. Например Activity/Fragment/Service в Android.