paint-brush
Внедрение зависимостей с помощью Dagger 2: что это такое, основные понятия и многое другоек@dilip2882
412 чтения
412 чтения

Внедрение зависимостей с помощью Dagger 2: что это такое, основные понятия и многое другое

к Dilip Patel21m2024/08/28
Read on Terminal Reader

Слишком долго; Читать

Внедрение зависимостей (DI) — это шаблон проектирования, используемый для реализации инверсии управления (IoC). Это метод, при котором ответственность за создание объектов передается другим частям кода. Это способствует слабой связанности, делая код более модульным и более простым в управлении.
featured image - Внедрение зависимостей с помощью Dagger 2: что это такое, основные понятия и многое другое
Dilip Patel HackerNoon profile picture
0-item


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

Внедрение зависимостей (DI) — это шаблон проектирования, используемый для реализации инверсии управления (IoC), где управление созданием и управлением зависимостями передается от приложения внешней сущности. Это помогает создавать более модульный, тестируемый и поддерживаемый код. Это метод, при котором ответственность за создание объектов передается другим частям кода. Это способствует слабой связанности, делая код более модульным и простым в управлении.

Классам часто нужны ссылки на другие классы для правильного функционирования. Например, рассмотрим класс Library , которому требуется класс Book . Эти необходимые классы называются зависимостями. Класс Library зависит от наличия экземпляра класса Book для работы.

hyperskill.org

Существует три основных способа, с помощью которых класс может получить необходимые ему объекты:

  1. Самостоятельное построение : класс создает и инициализирует свои собственные зависимости. Например, класс Library создаст и инициализирует свой собственный экземпляр класса Book .
  2. Внешний поиск : класс извлекает зависимости из внешнего источника. Некоторые API Android, такие как Context getters и getSystemService() , работают таким образом.
  3. Внедрение зависимостей : Зависимости предоставляются классу либо при его создании, либо через методы, которые их требуют. Например, конструктор Library получит экземпляр Book в качестве параметра.

Третий вариант — внедрение зависимостей! С помощью DI вы предоставляете зависимости класса, а не заставляете экземпляр класса получать их самостоятельно.

Пример без внедрения зависимости

Без DI Library , создающая собственную зависимость Book , может выглядеть следующим образом:

 class Library { private Book book = new Book(); void open() { book.read(); } } public class Main { public static void main(String[] args) { Library library = new Library(); library.open(); } }

Это не пример DI, поскольку класс Library создает свой собственный Book . Это может быть проблематично, потому что:

  • Тесная связь : Library и Book тесно связаны. Экземпляр Library использует один тип Book , что затрудняет использование подклассов или альтернативных реализаций.
  • Трудности тестирования : жесткая зависимость от Book усложняет тестирование. Library использует реальный экземпляр Book , предотвращая использование тестовых двойников для модификации Book для различных тестовых случаев.

Пример с внедрением зависимости

При использовании DI вместо того, чтобы каждый экземпляр Library создавал свой собственный объект Book , он получает объект Book в качестве параметра в своем конструкторе:

 class Library { private Book book; Library(Book book) { this.book = book; } void open() { book.read(); } } public class Main { public static void main(String[] args) { Book book = new Book(); Library library = new Library(book); library.open(); }

Основная функция использует Library . Поскольку Library зависит от Book , приложение создает экземпляр Book и затем использует его для построения экземпляра Library . Преимущества этого подхода на основе DI следующие:

  • Повторное использование Library : Вы можете передавать различные реализации Book в Library . Например, вы можете определить новый подкласс Book с именем EBook , который вы хотите, чтобы Library использовала. С DI вы просто передаете экземпляр EBook в Library , и он работает без каких-либо дополнительных изменений.
  • Простое тестирование Library : вы можете передавать тестовые дубликаты для проверки различных сценариев.

Еще один пример DI

Рассмотрим сценарий, в котором класс NotificationService опирается на класс Notification . Без DI NotificationService напрямую создает экземпляр Notification , что затрудняет использование различных типов уведомлений или тестирование службы с различными реализациями уведомлений.

Чтобы проиллюстрировать DI, давайте рефакторизируем этот пример:

 interface Notification { void send(); } class EmailNotification implements Notification { @Override public void send() { // Send email notification } } class SMSNotification implements Notification { @Override public void send() { // Send SMS notification } } class NotificationService { void sendNotification(Notification notification) { notification.send(); } }

Теперь NotificationService зависит от интерфейса Notification , а не от конкретного класса. Это позволяет использовать различные реализации Notification взаимозаменяемо. Вы можете задать реализацию, которую хотите использовать, с помощью метода sendNotification :

 NotificationService service = new NotificationService(); service.sendNotification(new EmailNotification()); service.sendNotification(new SMSNotification());

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

Существует три основных типа ДИ:

  1. Метод (интерфейс) инъекции : Зависимости передаются через методы, к которым класс может получить доступ через интерфейс или другой класс. Предыдущий пример демонстрирует метод инъекции.
  2. Внедрение через конструктор : зависимости передаются классу через его конструктор.
 class NotificationService { private final Notification notification; public NotificationService(Notification notification) { this.notification = notification; } public void sendNotification() { notification.send(); } } public class Main { public static void main(String[] args) { NotificationService service = new NotificationService(new EmailNotification()); service.sendNotification(); } }

3. Field Injection (или Setter Injection) : Определенные классы фреймворка Android, такие как действия и фрагменты, создаются системой, поэтому внедрение конструктора невозможно. При внедрении поля зависимости создаются после создания класса.

 class NotificationService { private Notification notification; public Notification getNotification() { return notification; } public void setNotification(Notification notification) { this.notification = notification; } public void sendNotification() { notification.send(); } } public class Main { public static void main(String[] args) { NotificationService service = new NotificationService(); service.setNotification(new EmailNotification()); service.sendNotification(); } }

4. Внедрение метода : зависимости предоставляются через методы, часто с использованием аннотации @Inject .

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

  • Классы становятся более многоразовыми и менее зависимыми от конкретных реализаций. Это происходит из-за инверсии управления, когда классы больше не управляют своими зависимостями, а работают с любой предоставленной конфигурацией.
  • Зависимости являются частью поверхности API и могут быть проверены при создании объекта или во время компиляции, что упрощает рефакторинг.
  • Поскольку класс не управляет своими зависимостями, во время тестирования могут передаваться различные реализации для охвата различных сценариев.

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

В предыдущем примере вы вручную создавали, предоставляли и управляли зависимостями различных классов без использования библиотеки. Этот подход известен как ручное внедрение зависимостей. Хотя он работает в простых случаях, он становится громоздким по мере увеличения числа зависимостей и классов. Ручное внедрение зависимостей имеет несколько недостатков:

  • Шаблонный код : для больших приложений управление всеми зависимостями и их правильное подключение может привести к большому количеству повторяющегося кода. В многослойной архитектуре создание объекта для верхнего слоя требует предоставления всех зависимостей для нижних слоев. Например, для сборки компьютера вам понадобятся ЦП, материнская плата, ОЗУ и другие компоненты; а ЦП может потребовать транзисторы и конденсаторы.
  • Сложное управление зависимостями : если вы не можете заранее создать зависимости — например, с помощью ленивой инициализации или ограничения объектов определенными потоками в вашем приложении — вам необходимо написать и поддерживать пользовательский контейнер (или граф зависимостей) для управления временем существования ваших зависимостей в памяти.

Библиотеки могут автоматизировать этот процесс, создавая и предоставляя вам зависимости. Эти библиотеки делятся на две категории:

  1. Решения на основе рефлексии : они связывают зависимости во время выполнения.
  2. Статические решения : они генерируют код для подключения зависимостей во время компиляции.

Dagger — популярная библиотека внедрения зависимостей для Java, Kotlin и Android, поддерживаемая Google. Dagger упрощает DI в вашем приложении, создавая и управляя графом зависимостей для вас. Он предоставляет полностью статические зависимости времени компиляции, решая многие проблемы разработки и производительности, связанные с решениями на основе отражения, такими как Guice.

Решения, основанные на рефлексии

Эти фреймворки подключают зависимости во время выполнения:

  1. Toothpick : фреймворк DI времени выполнения, который использует рефлексию для соединения зависимостей. Он разработан, чтобы быть легким и быстрым, что делает его подходящим для приложений Android.

Статические решения

Эти фреймворки генерируют код для подключения зависимостей во время компиляции:

  1. Hilt : Созданный на основе Dagger, Hilt предоставляет стандартный способ внедрения инъекции зависимостей Dagger в приложение Android. Он упрощает настройку и использование Dagger, предоставляя предопределенные компоненты и области действия .
  2. Koin : легкий и простой фреймворк DI для Kotlin. Koin использует DSL для определения зависимостей и прост в настройке и использовании.
  3. Kodein : DI-фреймворк на основе Kotlin, который прост в использовании и понимании. Он предоставляет простой и гибкий API для управления зависимостями.

Альтернативы внедрению зависимостей

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

 object ServiceLocator { fun getProcessor(): Processor = Processor() } class Computer { private val processor = ServiceLocator.getProcessor() fun start() { processor.run() } } fun main(args: Array<String>) { val computer = Computer() computer.start() }

Шаблон локатора служб отличается от внедрения зависимостей тем, как потребляются зависимости. С шаблоном локатора служб классы запрашивают необходимые им зависимости; с внедрением зависимостей приложение заранее предоставляет требуемые объекты.

Что такое Кинжал 2?

Dagger 2 — популярный фреймворк DI для Android. Он использует генерацию кода во время компиляции и известен своей высокой производительностью. Dagger 2 упрощает процесс внедрения зависимостей, генерируя необходимый код для обработки зависимостей, сокращая шаблонный код и повышая эффективность.

Dagger 2 — это библиотека на основе аннотаций для внедрения зависимостей в Android. Вот основные аннотации и их цели:

  • @Module : Используется для определения классов, которые предоставляют зависимости. Например, модуль может предоставить ApiClient для Retrofit.
  • @Provides : Аннотирует методы в модуле, чтобы указать, как создавать и возвращать зависимости.
  • @Inject : Используется для запроса зависимостей. Может применяться к полям, конструкторам и методам.
  • @Component : Интерфейс, который связывает @Module и @Inject . Он содержит все модули и предоставляет конструктор для приложения.
  • @Singleton : обеспечивает создание единственного экземпляра зависимости.
  • @Binds : используется в абстрактных классах для предоставления зависимостей, похоже на @Provides но более лаконично.

Компоненты кинжала

Dagger может генерировать график зависимостей для вашего проекта, позволяя ему определять, где получать зависимости, когда это необходимо. Чтобы включить это, вам нужно создать интерфейс и аннотировать его с помощью @Component .

В интерфейсе @Component вы определяете методы, которые возвращают экземпляры нужных вам классов (например, BookRepository ). Аннотация @Component предписывает Dagger сгенерировать контейнер со всеми зависимостями, необходимыми для удовлетворения типов, которые он выставляет. Этот контейнер называется компонентом Dagger и содержит граф объектов, которые Dagger знает, как предоставить вместе с их зависимостями.


Пример

Давайте рассмотрим пример с использованием LibraryRepository :

  1. Аннотируйте конструктор : добавьте аннотацию @Inject к конструктору LibraryRepository , чтобы Dagger знал, как создать экземпляр LibraryRepository .
 public class LibraryRepository { private final LocalLibraryDataSource localDataSource; private final RemoteLibraryDataSource remoteDataSource; @Inject public LibraryRepository(LocalLibraryDataSource localDataSource, RemoteLibraryDataSource remoteDataSource) { this.localDataSource = localDataSource; this.remoteDataSource = remoteDataSource; } }

2. Аннотируйте зависимости : Аналогичным образом аннотируйте конструкторы зависимостей ( LocalLibraryDataSource и RemoteLibraryDataSource ), чтобы Dagger знал, как их создавать.

 public class LocalLibraryDataSource { @Inject public LocalLibraryDataSource() { // Initialization code } } public class RemoteLibraryDataSource { private final LibraryService libraryService; @Inject public RemoteLibraryDataSource(LibraryService libraryService) { this.libraryService = libraryService; } }

3. Определите компонент : создайте интерфейс, аннотированный @Component , для определения графа зависимостей.

 @Component public interface ApplicationComponent { LibraryRepository getLibraryRepository(); }

При сборке проекта Dagger генерирует для вас реализацию интерфейса ApplicationComponent , обычно называемую DaggerApplicationComponent .

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

Теперь вы можете использовать сгенерированный компонент для получения экземпляров ваших классов с автоматически внедренными их зависимостями:

 public class MainApplication extends Application { private ApplicationComponent applicationComponent; @Override public void onCreate() { super.onCreate(); applicationComponent = DaggerApplicationComponent.create(); } public ApplicationComponent getApplicationComponent() { return applicationComponent; } }

В вашей активности или фрагменте вы можете получить экземпляр LibraryRepository :

 public class MainActivity extends AppCompatActivity { @Inject LibraryRepository libraryRepository; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ((MainApplication) getApplication()).getApplicationComponent().inject(this); // Use the injected libraryRepository } }

Ключевые концепции в Dagger 2

1. Модули
∘ Ключевые концепции модулей
∘ Включение модулей в компоненты
2. Области применения
3. Компоненты
4. Зависимости компонентов
5. Привязки во время выполнения

1. Модули

Модули в Dagger 2 — это классы, аннотированные @Module , которые предоставляют зависимости компонентам. Они содержат методы, аннотированные @Provides или @Binds , чтобы указать, как создавать и предоставлять зависимости. Модули необходимы для организации и управления созданием объектов, которые нужны вашему приложению.

Ключевые концепции модулей

  1. Аннотация @Module: Эта аннотация используется для определения класса как модуля Dagger. Класс модуля содержит методы, которые предоставляют зависимости.
  2. @Provides Аннотация: Эта аннотация используется в методах внутри модуля, чтобы указать, что метод предоставляет определенную зависимость. Эти методы отвечают за создание и возврат экземпляров зависимостей.
  3. Аннотация @Binds: эта аннотация используется в абстрактных классах для привязки реализации к интерфейсу. Она более лаконична, чем @Provides и используется, когда модуль является абстрактным классом.

Пример модуля

 @Module public class NetworkModule { @Provides @Singleton Retrofit provideRetrofit() { return new Retrofit.Builder() .baseUrl("https://api.example.com") .addConverterFactory(GsonConverterFactory.create()) .build(); } @Provides @Singleton OkHttpClient provideOkHttpClient() { return new OkHttpClient.Builder() .addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)) .build(); } }

В этом примере NetworkModule — это класс, аннотированный @Module . Он содержит два метода, аннотированные @Provides , которые создают и возвращают экземпляры Retrofit и OkHttpClient .

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

Когда у вас есть интерфейс и его реализация, вы можете использовать @Binds для привязки реализации к интерфейсу. Это более лаконично, чем использование @Provides .

 public interface ApiService { void fetchData(); } public class ApiServiceImpl implements ApiService { @Override public void fetchData() { // Implementation } } @Module public abstract class ApiModule { @Binds abstract ApiService bindApiService(ApiServiceImpl apiServiceImpl); }

В этом примере ApiModule — абстрактный класс, аннотированный @Module . Метод bindApiService аннотирован @Binds для привязки ApiServiceImpl к ApiService .

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

Пример:

  • NetworkModule : предоставляет сетевые зависимости, такие как Retrofit и OkHttpClient .
  • DatabaseModule : предоставляет зависимости, связанные с базой данных, такие как RoomDatabase .
  • UIModule : предоставляет зависимости, связанные с пользовательским интерфейсом, такие как ViewModel и Presenter .

Включение модулей в компоненты

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

ApplicationComponent.java:

 @Singleton @Component(modules = {NetworkModule.class, DatabaseModule.class}) public interface ApplicationComponent { void inject(MyApplication application); }

В этом примере ApplicationComponent включает NetworkModule и DatabaseModule для предоставления зависимостей приложению.

2. Области применения

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

  • Область действия Singleton : обеспечивает единственный экземпляр зависимости на протяжении всего жизненного цикла приложения.
  • Область действия : обеспечивает единственный экземпляр зависимости в жизненном цикле действия.
  • Область действия фрагмента : обеспечивает единственный экземпляр зависимости в жизненном цикле фрагмента.

1. Область действия синглтона

Определение : Область действия @Singleton гарантирует, что будет создан и совместно использован на протяжении всего жизненного цикла приложения один экземпляр зависимости.

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

Пример:

 @Singleton @Component(modules = {NetworkModule.class, DatabaseModule.class}) public interface ApplicationComponent { void inject(MyApplication application); }

В этом примере аннотация @Singleton гарантирует, что экземпляры Retrofit и Database , предоставляемые NetworkModule и DatabaseModule являются синглтонами и используются совместно во всем приложении.

2. Область деятельности

Определение : @ActivityScope (настраиваемая область действия) гарантирует, что в жизненном цикле действия будет создан и совместно использован один экземпляр зависимости.

Эта область полезна для зависимостей, которые являются специфичными для действия и должны воссоздаваться каждый раз при повторном создании действия, например, для презентаторов или моделей представлений.

Пример :

 @Scope @Retention(RetentionPolicy.RUNTIME) public @interface ActivityScope { } @ActivityScope @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) public interface ActivityComponent { void inject(MainActivity mainActivity); }

В этом примере аннотация @ActivityScope гарантирует, что зависимости, предоставляемые ActivityModule , ограничены жизненным циклом действия.

3. Область действия фрагмента

Определение : @FragmentScope (еще одна настраиваемая область действия) гарантирует, что в жизненном цикле фрагмента создается и совместно используется один экземпляр зависимости.

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

Пример :

 @Scope @Retention(RetentionPolicy.RUNTIME) public @interface FragmentScope { } @FragmentScope @Component(dependencies = ActivityComponent.class, modules = FragmentModule.class) public interface FragmentComponent { void inject(MyFragment myFragment); }

В этом примере аннотация @FragmentScope гарантирует, что зависимости, предоставляемые FragmentModule ограничены жизненным циклом фрагмента.

3. Компоненты

Зависимости компонентов позволяют одному компоненту зависеть от другого, позволяя повторно использовать зависимости. Существует два основных типа зависимостей компонентов:

  • Компонент приложения : обеспечивает зависимости, необходимые для всего приложения.
  • Компонент действия : обеспечивает зависимости, необходимые в рамках определенного действия.

1. Компонент приложения

Определение : Компонент приложения предоставляет зависимости, которые необходимы во всем приложении. Обычно он ограничен @Singleton , чтобы гарантировать, что зависимости являются общими для всего приложения.

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

Пример :

 @Singleton @Component(modules = {NetworkModule.class, DatabaseModule.class}) public interface ApplicationComponent { void inject(MyApplication application); }

В этом примере ApplicationComponent отвечает за предоставление экземпляров Retrofit и Database , которые являются общими для всего приложения.

2. Компонент активности

Определение : Компонент Activity предоставляет зависимости, необходимые в рамках определенной активности. Обычно он ограничен пользовательской областью действия, например @ActivityScope , чтобы гарантировать, что зависимости будут воссоздаваться каждый раз при воссоздании активности.

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

Пример :

 @ActivityScope @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) public interface ActivityComponent { void inject(MainActivity mainActivity); }

В этом примере ActivityComponent зависит от ApplicationComponent и предоставляет зависимости, специфичные для MainActivity .

4. Зависимости компонентов

Зависимости компонентов позволяют одному компоненту зависеть от другого, позволяя повторно использовать зависимости. Существует два основных типа зависимостей компонентов:

  • Подкомпоненты : Подкомпонент является дочерним элементом другого компонента и может получать доступ к зависимостям своего родителя.
  • Атрибут зависимости : позволяет компоненту зависеть от другого компонента, не будучи при этом подкомпонентом.

1. Подкомпоненты:

Подкомпонент является дочерним элементом другого компонента и может иметь доступ к зависимостям своего родителя. Подкомпоненты определяются внутри родительского компонента и могут наследовать его область действия.

Пример :

 @ActivityScope @Subcomponent(modules = ActivityModule.class) public interface ActivitySubcomponent { void inject(MainActivity mainActivity); }

В этом примере ActivitySubcomponent является подкомпонентом родительского компонента и может получать доступ к его зависимостям.

2. Атрибут зависимости

Это позволяет компоненту зависеть от другого компонента, не будучи подкомпонентом. Зависимый компонент может получить доступ к зависимостям, предоставляемым родительским компонентом.

Пример :

 @ActivityScope @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) public interface ActivityComponent { void inject(MainActivity mainActivity); }

В этом примере ActivityComponent зависит от ApplicationComponent и может получить доступ к его зависимостям.

5. Привязки во время выполнения

Привязки времени выполнения в Dagger 2 относятся к предоставлению зависимостей, которые создаются и управляются во время выполнения на основе контекста, в котором они необходимы.

  • Контекст приложения : используется для зависимостей, которые должны существовать столько же, сколько и приложение.
  • Контекст активности : используется для зависимостей, которые должны существовать столько же, сколько и активность.

1. Контекст приложения

Определение : Контекст приложения — это контекст, привязанный к жизненному циклу всего приложения. Он используется для зависимостей, которые должны существовать так же долго, как и само приложение.

Зависимости, которые являются общими для всего приложения и не требуют повторного создания для каждой активности или фрагмента. Примерами являются сетевые клиенты, экземпляры баз данных и общие настройки.

Пример :

 @Module public class AppModule { private final Application application; public AppModule(Application application) { this.application = application; } @Provides @Singleton Application provideApplication() { return application; } @Provides @Singleton Context provideApplicationContext() { return application.getApplicationContext(); } }

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

2. Контекст деятельности

Определение : Контекст активности — это контекст, привязанный к жизненному циклу определенной активности. Он используется для зависимостей, которые должны существовать так же долго, как и сама активность.

Зависимости, которые являются специфическими для активности и должны быть созданы заново каждый раз при повторном создании активности. Примерами являются модели представлений, презентаторы и зависимости, связанные с пользовательским интерфейсом.

Пример :

 @Module public class ActivityModule { private final Activity activity; public ActivityModule(Activity activity) { this.activity = activity; } @Provides @ActivityScope Activity provideActivity() { return activity; } @Provides @ActivityScope Context provideActivityContext() { return activity; } }

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

Использование привязок времени выполнения в компонентах

Чтобы использовать эти привязки времени выполнения, вам необходимо включить соответствующие модули в ваши компоненты:

Компонент приложения :

 @Singleton @Component(modules = {AppModule.class, NetworkModule.class}) public interface ApplicationComponent { void inject(MyApplication application); Context getApplicationContext(); }

Компонент деятельности :

 @ActivityScope @Component(dependencies = ApplicationComponent.class, modules = ActivityModule.class) public interface ActivityComponent { void inject(MainActivity mainActivity); Context getActivityContext(); }

Внедрение контекстов

После настройки компонентов и модулей вы можете внедрять контексты в свои классы по мере необходимости.

Пример в действии :

 public class MainActivity extends AppCompatActivity { @Inject Context activityContext; @Inject Context applicationContext; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ApplicationComponent appComponent = ((MyApplication) getApplication()).getApplicationComponent(); ActivityComponent activityComponent = DaggerActivityComponent.builder() .applicationComponent(appComponent) .activityModule(new ActivityModule(this)) .build(); activityComponent.inject(this); // Use the injected contexts Log.d("MainActivity", "Activity Context: " + activityContext); Log.d("MainActivity", "Application Context: " + applicationContext); } }

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

Пример: использование Dagger 2 в приложении Android

Настройка Кинжала 2

Чтобы использовать Dagger 2 в вашем проекте, вам необходимо добавить следующие зависимости в ваш файл build.gradle :

 dependencies { implementation 'com.google.dagger:dagger:2.x' annotationProcessor 'com.google.dagger:dagger-compiler:2.x' }

Замените 2.x на последнюю версию Dagger 2.

Шаг 1: Определите модуль

Создайте модуль для предоставления зависимостей. Например, NetworkModule для предоставления экземпляра Retrofit :

 @Module public class NetworkModule { @Provides @Singleton Retrofit provideRetrofit() { return new Retrofit.Builder() .baseUrl("https://api.example.com") .addConverterFactory(GsonConverterFactory.create()) .build(); } }

Шаг 2: Определите компонент

Создайте компонент для соединения модуля и классов, которым нужны зависимости:

 @Singleton @Component(modules = {NetworkModule.class}) public interface ApplicationComponent { void inject(MyApplication application); }

Шаг 3: Внедрение зависимостей

Используйте компонент для внедрения зависимостей в ваши классы. Например, в вашем классе Application :

 public class MyApplication extends Application { private ApplicationComponent applicationComponent; @Override public void onCreate() { super.onCreate(); applicationComponent = DaggerApplicationComponent.builder() .networkModule(new NetworkModule()) .build(); applicationComponent.inject(this); } public ApplicationComponent getApplicationComponent() { return applicationComponent; } }

Шаг 4: использование внедренных зависимостей

Теперь вы можете использовать внедренные зависимости в своих классах. Например, в Activity :

 public class MainActivity extends AppCompatActivity { @Inject Retrofit retrofit; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ((MyApplication) getApplication()).getApplicationComponent().inject(this); // Use the injected Retrofit instance // ... } }

Заключение

Давайте подведем итог этой теме:

  • Основная цель DI — ослабить связанность, что упрощает управление зависимостями.
  • Используя DI, вы можете повысить гибкость кода и упростить процесс тестирования.
  • DI — сложная тема с различными реализациями в зависимости от сценария.
  • DI на разных языках имеет особенности, которые могут повлиять на то, как вы с ним работаете.
  • Dagger 2 автоматизирует процесс создания и предоставления зависимостей, сокращая объем шаблонного кода и улучшая удобство обслуживания.
  • Dagger 2 обеспечивает безопасность во время компиляции, гарантируя, что все зависимости будут удовлетворены до запуска приложения.
  • Генерируя код во время компиляции, Dagger 2 позволяет избежать снижения производительности, связанного с решениями на основе рефлексии.