Что такое scope у DI фреймворков?
Комментарии (2)
Ответ сгенерирован нейросетью и может содержать ошибки
Scope в контексте Dependency Injection (DI)
Scope в DI-фреймворках — это концепция, определяющая время жизни (lifetime) и область видимости создаваемых зависимостей. Проще говоря, scope контролирует, как долго существует экземпляр объекта и в каком контексте он переиспользуется между различными компонентами приложения.
Основные цели использования Scope
- Управление временем жизни: Определяет момент создания и уничтожения объекта.
- Контроль повторного использования: Указывает, когда нужно создавать новый экземпляр, а когда следует использовать существующий (шаблон Singleton в рамках определенной области).
- Изоляция состояний: Позволяет изолировать данные в рамках конкретного потока, экрана (Activity/Fragment) или пользовательской сессии.
- Эффективность: Оптимизирует использование памяти и процессорного времени за счет разумного переиспользования тяжеловесных объектов.
Распространенные типы Scope в Android (на примере Dagger/Hilt)
В Dagger и Hilt (официальная надстройка над Dagger для Android) scope реализуется с помощью аннотаций. Каждая аннотация привязывает жизнь зависимости к жизни компонента (Component), который ее предоставляет.
// Объявление кастомного Scope-аннотации в Dagger
@Scope
@MustBeDocumented
@Retention(AnnotationRetention.RUNTIME)
annotation class MyCustomScope
// Применение Scope к реализации
@MyCustomScope
@Provides
fun provideHeavyObject(): HeavyObject {
return HeavyObject()
}
Стандартные scopes в Hilt:
-
@Singleton: Жизнь объекта привязана к жизни Application. Создается один раз и существует всё время работы приложения. Идеален для глобальных зависимостей (Retrofit, база данных, репозиторий).@Module @InstallIn(SingletonComponent::class) object AppModule { @Singleton @Provides fun provideRetrofit(): Retrofit { return Retrofit.Builder() .baseUrl("https://api.example.com/") .build() } } -
@ActivityScoped: Объект живет до тех пор, пока существует привязанная к нему Activity. Все инъекции в одну и ту же Activity получат один и тот же экземпляр.@Module @InstallIn(ActivityComponent::class) object ActivityModule { @ActivityScoped @Provides fun provideActivityDependency(): MyDependency { return MyDependency() } } -
@ViewModelScoped: Жизнь объекта привязана к конкретному ViewModel. Очень полезен для зависимостей, которые должны переживать изменения конфигурации (поворот экрана) вместе с ViewModel.@Module @InstallIn(ViewModelComponent::class) object ViewModelModule { @ViewModelScoped @Provides fun provideScopedToViewModel(): ViewModelSpecificDep { return ViewModelSpecificDep() } } -
@FragmentScoped/@ViewScoped: Аналогично, но для жизни Fragment или View.
Принцип работы и важные нюансы
- Иерархия компонентов: Scopes в Dagger/Hilt строятся на иерархии компонентов. Компонент более высокого уровня (например,
SingletonComponent) является родителем для компонента более низкого уровня (например,ActivityComponent). Это означает:
* Зависимости, доступные в `SingletonComponent`, могут быть инжектированы в `ActivityComponent`.
* Обратное неверно. Зависимость, созданная в `ActivityComponent`, не может быть предоставлена `SingletonComponent`.
-
Связка Scope и Component: Каждый scope аннотирует определенный тип компонента. Например, аннотация
@Singletonможет использоваться только в модулях, установленных вSingletonComponent::class. -
Отсутствие аннотации (Unscoped): Если зависимость не помечена scope-аннотацией, DI-фреймворк будет создавать новый экземпляр каждый раз, когда она запрашивается. Это поведение по умолчанию.
Пример на практике: Разные Scope для разных задач
Представьте приложение для онлайн-магазина:
@Singleton:Retrofit,RoomDatabase,SharedPreferences— создаются один раз.@ActivityScoped:Navigatorдля управления переходами между экранами в рамках одной Activity.@ViewModelScoped:CartManagerдля корзины покупок, которая должна сохраняться при повороте экрана конкретного товара.- Без Scope (Unscoped):
ProductItem— DTO объект, который уникален для каждого элемента в списке.
Вывод: Правильное использование scope — это краеугольный камень эффективной архитектуры Android-приложения с DI. Оно позволяет четко управлять ресурсами, предотвращать утечки памяти (например, не держать ссылку на Activity в синглтоне) и писать предсказуемый, тестируемый код, где жизненные циклы всех объектов находятся под полным контролем разработчика.