Что такое dagger 2
Dagger 2 для начинающих Android разработчиков — Введение
Данная статья является первой частью серии статей, предназначенных, по словам автора, для тех, кто не может разобраться с внедрением зависимостей и фреймворком Dagger 2, либо только собирается это сделать. Оригинал написан 18 ноября 2017 года. Изображения и GIF — из оригинала. Перевод вольный.
Dagger 2 — это полностью статический фреймворк для внедрения зависимостей в Java и Android, работающий во время компиляции. Dagger 2 — это адаптация созданного ранее компанией Square фреймворка Dagger, поддерживаемая компанией Google.
Для кого эта статья?
Если вы начинающий Android разработчик, изучающий непосредственно Android и параллельно получающий знания по Java, то данная статья для вас. Если вы пытались изучать Dagger 2 и то, что вы находили в интернете казалось вам немного сложным — не беспокойтесь, я тоже прошел через это (все мы немного особенные, каждому требуется свой подход в объяснении чего-либо) и эта статья определенно для вас. Если вы уже знакомы с внедрением зависимостей и Dagger, то вы сможете узнать что-нибудь новое или прояснить для себя некоторые вещи.
Серия статей
Требования
Предполагается, что вы уже знакомы с языком программирования Java, принципами ООП и Android разработкой.
Что такое зависимость?
Замечание: для объяснения данной концепции я буду использовать аналогии с телесериалом «Игра престолов». Если вы не знакомы с данным сериалом, то можете заменять имена классов на более удобные для вас. И вы должны обязательно начать смотреть этот сериал.
Чем плохи зависимости?
Большое количество зависимостей в классе приводит к проблемам сильных связей (hard dependency), что плохо по следующим причинам:
#Повторное использование (reusablility)
Когда классы и методы слабо связаны, не связаны или не зависят от множества других — возможности повторного использования кода возрастают. Повторное использование кода — одна из базовых идей объектно-ориентированного программирования.
#Тестирование
#Поддерживаемость (maintainability)
Когда код не может быть корректно протестирован, его части невозможно или сложно повторно использовать, а проект продолжает расти, тогда проект становится очень сложно поддерживать. Поддерживаемость зависит от множества других факторов, но пока новые разработчики в вашей команде понимают систему и делают работу коллег комфортнее, то ваш проект всё ещё поддерживаемый.
Типы зависимостей
Есть множество типов зависимостей, но можно выделить основные:
#Зависимость от классов
#Зависимость от интерфейсов
Метод executePlan принимает интерфейс WarStrategy как зависимость. WarStrategy может быть реализован всеми домами (Targaryens, Starks, Lannisters и так далее).
#Зависимость от методов или полей
#Прямые и косвенные зависимости
Резюме
Зависимости плохи тем, что уменьшают возможности повторного использования кода, а также усложняют процесс тестирования, из-за чего поддерживать проект становится сложнее.
Зависимости могут быть различных типов: классовые, интерфейсные, от методов, от полей, прямые, косвенные и другие.
Понимание Dagger 2
Ниже вы увидите вольный перевод статьи Miquel Beltran, опубликованной на
Medium 12 февраля 2016 года. Целью статьи является формирование понимания базового механизма работы Dagger 2.
Я обнаружил, что большинство руководств, объясняющих как работает Dagger, слишком сложны. Даже руководство Google/Square слишком тяжелое для понимания, если у вас нет четкого представления о том, как работает внедрение зависимостей.
Чтобы понять это, я создал очень простой Java-проект с несколькими классами, который показывает, как работает Dagger.
В этой статье я объясню основные составляющие Dagger. Статья нацелена на тех, кто не пользуется данной библиотекой, но планирует.
Структура проекта
Это код файла build.gradle. Будет использоваться стандартный java плагин и JUnit для создания модульных тестов. Не забудьте добавить в зависимости библиотеку dagger-compiler (это была моя первая ошибка).
Замечание от foal по поводу зависимости dagger-compiler:
Для Dagger‡ и других APT-based продуктов лучше пользоваться gradle-apt-plugin. Это сделает компиляцию вашего проекта более прозрачной и безопасной, так как не будут путаться два разных classpath’а — один для компиляции вашего кода и второй для его генерации.
Покажите мне код
В этом примере будет два класса:
Теперь нужно создать классы, которые определят, как будет реализовано внедрение зависимостей, это Component и Module.
Использование Component
Следующий модульный тест показывает, как внедрить класс GameData в созданный экземпляр класса GameSession, используя сгенерированный класс типа Component.
Dagger 2 для начинающих Android разработчиков. Dagger 2. Часть 1
Данная статья является четвертой частью серии статей, предназначенных, по словам автора, для тех, кто не может разобраться с внедрением зависимостей и фреймворком Dagger 2, либо только собирается это сделать. Оригинал написан 10 декабря 2017 года. Перевод вольный.
Это четвертая статья цикла «Dagger 2 для начинающих Android разработчиков.». Если вы не читали предыдущие, то вам сюда.
Серия статей
Ранее в цикле статей
В последней статье мы поняли, что класс не должен создавать зависимости. Вместо этого, он должен получать их снаружи.
Также мы рассмотрели простой пример внедрения зависимостей в действии. Взяли пример с битвой бастардов и попытались избавиться от сильных связей (hard dependencies) через внедрение зависимостей.
Как внедрение зависимостей может усложниться?
Если проект такой же простой, как ранее рассмотренный пример, то создание экземпляров и внедрение небольшого количества зависимостей вручную, через точку входа ( main() или onCreate() метод) в программу очень разумно. Однако, во многих проектах есть множество классов, у каждого из которых есть различные зависимости, которые нужно удовлетворить. Для создания экземпляров и подключения всего вместе требуется большое количество кода. Ещё хуже то, что этот код будет постоянно изменяться каждый раз при добавлении новых классов в приложение и при изменении существующих классов для того, чтобы внедрить новые зависимости.
Чтобы проиллюстрировать описанную проблему давайте немного усложним наш пример. Во время войны, в битве бастардов ( BattleOfBastards ) вероятно потребуется помощь союзников ( Allies ). Также железный банк ( IronBank ) будет финансировать дома. Измененный главный метод будет выглядеть следующим образом:
Очень быстро точка входа в приложение будет наполнена огромным количеством кода для инициализации всех зависимостей. Для создания одного класса, с которым мы будем работать, нужно инициализировать несколько других. По мере роста приложения и добавления в него новых классов, точка входа в приложение будет раздуваться и в конечном итоге этот метод станет очень сложно поддерживать.
Dagger 2 спешит на помощь
Dagger 2 — это один из фреймворков с открытым исходным кодом для внедрения зависимостей (далее буду использовать DI, от Dependency Injection), который генерирует большое количество шаблонного кода за вас. Почему он лучше остальных? Сейчас это единственный DI фреймворк, который генерирует полностью отслеживаемый Java код, имитирующий тот код, который вы могли написать вручную. Это означает, что в построении графа зависимостей нет никакой магии. Dagger 2 менее динамичен, чем другие (в нем не используется рефлексия), но простота и производительность сгенерированного кода находятся на том же уровне, что и у написанного вручную. Коротко, Dagger 2 генерирует весь шаблонный код для внедрения зависимостей за вас.
Понимание обработчиков аннотаций (Annotation Processors)
#Аннотации
Аннотации — это вид метаданных, который может быть связан с классами, методами, полями и даже другими аннотациями. Аннотации используются в Java для предоставления дополнительной информации, как альтернатива XML или маркерными интерфейсам (пустые интерфейсы). К аннотациям можно получить доступ и в процессе выполнения программы (runtime) через механизм рефлексии.
#Обработчики аннотаций (Annotation Processors)
Обработчики аннотаций — это генераторы кода, которые скрывают от вас шаблонный код, создавая его за вас во время компиляции. Пока эти действия выполняются во время компиляции, никакого отрицательного влияния на производительность нет.
#Почему я должен знать об обработчиках аннотаций?
Dagger 2 использует их. Таким образом, можно отследить весь сгенерированный код во время компиляции. Следовательно, нет ухудшения производительности, а ошибки легко отслеживаются.
#Примеры
Аннотации Dagger 2
@Inject
Это наиболее важная аннотация. JSR-330 определяет данную аннотацию как пометку для зависимостей, которые должны быть предоставлены фреймворком для внедрения зависимостей.
Другими словами, аннотация @Inject сообщит Dagger какие зависимости должны быть предоставлены зависимому объекту. Это как агенты железного банка, которые ведут переговоры с домами и определяют сумму кредита, которую могут предоставить дому.
@Component
Другими словами, эта аннотация как агент железного банка, который отвечает за утверждение кредита и перевод денег на соответствующий счет.
Убить Белых Ходоков валирийским мечом
Настройка Dagger 2
Добавление аннотации @Inject
План — внедрить зависимости Starks и Boltons в класс War с помощью Dagger 2. О чём мы должны явно ему сказать. Ниже пример того, как это нужно делать, используя внедрение с использованием конструктора.
Добавление аннотации @Component
Как мы узнали ранее, @Component — это мост между генерируемым кодом и зависимостями. Также @Component говорит Dagger 2 как необходимо внедрять зависимость. Сделаем интерфейс BattleComponent внутри класса BattleOfBastards (можно сделать и отдельно).
Теперь необходимо пересобрать (rebuild) проект!
Поздравляю! Вы создали первый проект с использованием Dagger 2. Я очень ценю то, что вы нашли на это время и зашли так далеко. Время отпраздновать.
Резюме
Мы обсудили как ручное использование DI усложняет работу и увеличивает количество шаблонного кода. Далее обсудили как Dagger 2 помогает нам избавиться от этой боли и сам генерирует шаблонный код.
После разобрали информацию по обработчикам аннотаций и базовым аннотациям Dagger 2 ( @Inject и @Component ). Затем применили аннотации в нашем примере и внедрили зависимости, используя Dagger 2.
Dagger 2 – это элементарно (Часть 1)
Введение
Что такое Dependency Injection
Dependency Injection (инъекция или внедрение зависимости) — это зависимость одного класса от другого. т.е. для полноценной работы одного класса нужна инициализация другого(их) класса.
Например, класс Car (автомобиль) не может работать без класса Engine (Мотор) который в свою очередь не может работать без класса Fuel (Топливо). Выглядит это так:
В данном примере класс Car зависит от класса Engine а тот в свою очередь от класса Fuel.
Dagger 2 – введение
Dagger это библиотека которая помогает реализовать «внедрение зависимости:. Это библиотека google. Подробную документацию можно получить тут.
Первое использование Dagger 2
В первую очередь нужно добавить dagger в приложение. Знаю 2 методов как это можно сделать
1. Открыть build.gradle (App) и добавить след.
1.1 В самом верху в разделе объявления plugin
1.2 в разделе dependencies
версию dagger (dagger_version) указываю в разделе
Если такого еще нет, раздел нужно добавить над разделом android.
2. Добавить Maven репозиторий через Project Structure — Dependencies — Add library dependencies
После синхронизации проекта мы готовы к внедрению зависимостей при помощи dagger.
В первую очередь создадим классы Car, Engine и Fuel:
Перед констракторами классов Car, Engine и Fuel добавим аннотация dagger Inject, тем самым дадим понять dagger что эти классы должны быть внедрены при необходимости. Получаем след.
Dagger должен знать как создавать все объекты которые он должен внедрять. Для того чтоб перечислить все классы которые мы внедряем (Inject) используется аннотация Component которая объявляется для интерфейса (DaggerComponent).
при объявлении методов компонента важны не названия методом а возвращаемый ими класс.
На этом шаге нужно собрать проект (Build — Rebuild project). После этого dagger сгенерирует необходимые классы и фабрику для инициализации компонентов. Название фабрики будет совпадать с названием интерфейса в которой мы инициализируем классы для даггер за исключением того что будет добавлен префикс „Dagger“, т.е. на выходе получим класс DaggerDaggerComponent.
Все готово. Попробуем создать поле car типа Car в MainActivity:
Запустив приложение можно убедиться что поле car инициализируется при обращении к нему
Дружественное введение в Dagger 2. Часть 1
Что такое внедрение зависимостей, что представляет собой Dagger и как он может пригодиться нам в написании более чистого и простого в тестировании кода.
Дисклеймер от переводчика. Данный перевод выполнен в целях самообразования, а на Хабре выложен в предположении, что многим начинающим Android-девелоперам, которым, как и мне, не довелось родиться Java-говорящим разработчиком в пятом поколении, достаточно сложно разобраться в конечных продуктах многолетних наслоений концепций и методов разработки. Эта серия статей — отличный пример того, как нужно объяснять сложные вещи, и, надеюсь, Вам она понравится не меньше, чем мне. Обо всех замеченных ошибках и неточностях прошу сообщать в личку.
Внедрение зависимостей (Dependency injection, DI) — великолепная техника, упрощающая покрытие приложения тестами, а Dagger 2 — один из самых популярных Java/Android фреймворков, предназначенных для этой цели. При этом большинство вводных курсов по Dagger 2 исходят из предположения, что читатель уже хорошо знаком с DI и его достаточно сложной терминологией, затрудняющей вхождение для новичков.
В этой серии статей я попытаюсь представить вам более дружественное введение в Dagger 2 со множеством примеров уже готового для компиляции кода.
Дабы не растекаться мыслью по древу, я умышленно оставляю за бортом историю DI-фреймворков, ровно как и их бесчисленные разновидности. Начнем мы с внедрения в конструктор класса, а на остальные варианты обратим взор по мере продвижения.
Итак, что же такое внедрение зависимостей?
Внедрение зависимостей — это техника, упрощающая тестирование и переиспользование классов. Давайте рассмотрим на примере, как его можно применить. Допустим, нам требуется написать приложение, которое печатает в консоль текущие погодные условия. Простейшая реализация могла бы выглядеть примерно так:
Некоторые методы намеренно опущены, поскольку в данном случае неважны. Заметьте, что WeatherReporter‘у для выполнения собственной работы необходимы 2 объекта: LocationManager, опеределяющий местоположение пользователя, и WeatherService, выдающий температуру по заданным координатам.
В хорошо спроектированном объектно-ориентированном приложении на каждом объекте лежит лишь небольшая доля обязанностей, остальная работа делегируется другим объектам. Вот эти другие объекты и называются зависимостями. До того, как объект начнет делать какую-то реальную работу, все его зависимости должны быть каким-то образом разрешены. Например, для нашего WeatherReporter зависимости разрешаются путем создания новых экземпляров этих объектов в конструкторе.
Для небольших приложений инициализация зависимостей в конструкторе класса работает вполне неплохо, но по мере разрастания приложения у этого подхода обнаруживается ряд недостатков. Во-первых, это делает класс менее гибким. Например, если приложение должно быть мультиплатформенным, нам может понадобиться заменить наш текущий LocationManager другим, но сделать это будет не так просто. Возможно, нам захочется использовать один и тот же LocationManager в нескольких местах, но и это будет трудно выполнить, пока мы не изменим класс.
Во-вторых, наш класс не поддается изолированному тестированию. Создание одного объекта WeatherReporter влечет за собой создание двух других объектов, которые в конечном счете подпадают под тестирование вместе с оригинальным объектом. Это может стать серьезной проблемой, если одна из зависимостей зависит от дорогостоящего внешнего ресурса (интернет-соединения, например) или у нее самой имеется большое количество зависимостей.
Корень зла в данном случае — то, что наш класс выполняет две разных обязанности. Он обязан знать не только как выполнить свою задачу, но и где найти компоненты, необходимые для ее выполнения. Если вместо этого мы предоставим объекту все необходимое для выполнения его работы, указанная проблема исчезнет. Также это облегчает взаимодействие класса с другими компонентами приложения, не говоря уже об упрощении тестирования.
Этот подход называется внедрение зависимостей. В приложении, которое полагается на DI, объектам не приходится «рыскать вокруг» в поисках зависимостей или самим их создавать. Все зависимости, которые им предоставляются (внедряются), уже готовы к использованию.
Построение графа
Разумеется, в какой-то момент кто-нибудь должен проинициализировать все зависимости и предоставить их тем объектам, которые в них нуждаются. Этот этап, именуемый построением графа зависимостей, обычно выполняется в точке входа в приложение. В десктопном приложении, например, этот код располагается внутри метода main, как в примере ниже. В Андроид-приложении это можно сделать внутри метода onCreate activity.
Если проект прост, как в нашем предыдущем примере, инициализация и внедрение нескольких зависимостей в методе main оправданы. Однако большинство проектов состоит из множества классов со своими зависимостями, которые должны быть разрешены. Инициализация и связывание всего этого вместе требует написания большого количества кода. Даже хуже, этот код будет регулярно изменяться при добавлении нового класса в приложение или добавлении новой зависимости в существующий класс.
Чтобы проиллюстрировать эту проблему, давайте превратим наш пример в более реалистичный. На практике классу WeatherService понадобится, скажем, WebSocket для коммуникации с сетью. LocationManager‘у понадобится GPSProvider для взаимодействия с железом. Помимо прочего, большинству классов понадобится Logger для вывода отладочной информации в консоль. Модифицированный метод main выглядит теперь так:
Очень быстро точка входа в наше приложение начала раздуваться от обилия инициализирующего кода. Чтобы создать единственный реально необходимый нам объект WeatherReporter, нам приходится вручную инициализировать множество других объектов. По мере того как приложение будет разрастаться и обрастать классами, метод main также продолжит разбухать, пока однажды не станет совершенно неподдерживаемым.
Как может помочь Dagger 2?
Dagger 2 — это инструмент с открытым исходным кодом, генерирующий большую часть инициализирующего кода за нас, основываясь всего на нескольких аннотациях. При использовании Dagger 2 точка входа в наше приложение может быть написана всего в несколько строк кода, независимо от того, сколько классов у нас есть и сколько в них присутствует зависимостей. Ниже новый метод main для нашего примера с использованием Dagger.
Заметьте, что мы не обязаны теперь писать код для разрешения зависимостей или указывать как эти зависимости переплетаются между собой. Это уже сделано за нас. Класс DaggerAppComponent, автоматически генерируемый во время компиляции проекта, достаточно умен, чтобы знать, что классу WeatherReporter нужны Logger, LocationManager и WeatherService, которому в свою очередь нужны GPSProvider и WebSocket. При вызове метода getWeatherReporter он создаст все эти объекты в верной последовательности, создаст между ними связи и вернет только то, что нам требовалось.
Чтобы Dagger заработал, мы должны выполнить несколько шагов. Во-первых, добавить аннотацию в конструктор каждого класса, о котором Dagger должен знать. Ниже пример того, как сделать это для одного конкретного класса, но точно так же необходимо аннотировать все классы, которым требуются какие-то зависимости или которые сами выступают зависимостями для других классов.
Если вы переживаете по поводу загрязнения приложения специфичным для Dagger’а кодом, вам будет приятно узнать, что аннотация Inject стандартизирована (JSR 330) и с ней работают многие другие инструменты, навроде Spring или Guice. Соответственно, переключение на другой DI-фреймворк не потребует изменения множества классов в приложении.
На следующем шаге нам необходимо создать интерфейс с аннотацией Component, объявляющий методы, которые будут возвращать необходимые нам объекты. Необязательно объявлять здесь методы для каждого класса в нашем проекте, обязательны только методы для классов, непосредственно используемых в точке входа в приложение. В нашем примере методу main нужен только WeatherReporter, так что объявляем в интерфейсе только один метод.
Все, что осталось — интегрировать Dagger с нашей системой сборки. При использовании Gradle просто добавляем новые зависимости в build.gradle.
Все, проект может быть скомпилирован и выполнен. Если хотите попробовать сделать это сами, исходники для нашего примера доступны на Github.
В качестве заключения. Обратите внимание: мы все еще можем использовать любой класс без Dagger, как и прежде. Например, мы все еще можем инициализировать Logger вручную посредством оператора new, скажем, в юнит-тесте. Dagger не изменяет поведение языка, он просто генерирует удобные классы, которые выполняют инициализацию объектов за нас.
В следующей статье из этой серии мы посмотрим, что случается когда одна и та же зависимость может быть использована во множестве классов или когда мы по каким-то причинам не можем аннотировать класс.
Что почитать
Если внедрение зависимостей кажется вам бесполезной техникой или вы хотите увидеть больше примеров, когда DI упрощает тестирование, обратите внимание на потрясающее руководство Writing Testable Code (pdf) авторства Miško Hevery, Russ Ruffer и Jonathan Wolter.
Если вас больше интересует теория внедрения зависмостей и все его разновидности, рекомендую эссе Inversion of Control Containers and the Dependency Injection pattern (перевод на русский — ч.1, ч.2) Мартина Фаулера.