Stone. Квалификаторы и идентификаторы

от автора

Использование библиотеки схема компонентов окна ожидания такси

схема компонентов окна ожидания такси

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

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

Свободное кресло разработчика

Ничего сложного нет в простой генерации компонентов под каждый экран (в данном случае примем единицей DI скоупа), автор бы хотел немного теперь усложнить схему наличием еще одного экрана, который использует те же интеракторы и репозитории, вынуждая их делать синглтонами приложения. Или локальными синглтонами, но это явление будем считать больше исключением чем практикой.

Оба репозитория переиспользуются на 2х экранах

Оба репозитория переиспользуются на 2х экранах

Теперь наши компоненты: репозитории и датасорс, гвоздями прибиты к жизненному циклу приложения. Для разработки удобно, что они доступны для любого экрана. Но вот для масштабирования дела обстоят иначе. Синглтон просто так не задублируешь. Теперь их надо различать между собой в DI.

Поваренная книга

И вот мы планомерно пришли к знакомству с инструментарием Stone библиотеки — квалификаторами. Думаю вы уже с ними знакомы и из других библиотек DI, но все же уточню правило их использования.

@Qualifier @Retention(RetentionPolicy.RUNTIME) @Documented annotation class MainTripQualifier

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

@MainTripQualifier @Provide(cache = Provide.CacheType.Strong) abstract fun provideTripInfoRepositoryMain(     api: TripInfoApi,     @MainTripQualifier cache: TripInfoInMemory, ): TripInfoRepository

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

Разделение приложения квалификаторами DI

Разделение приложения квалификаторами DI

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

@Module abstract class InteractorsModule {      @MainTripQualifier     abstract fun provideTripInfoInteractorMain(         @MainTripQualifier         ordersRepository: CurrentOrderRepository,         @MainTripQualifier         tripInfoRepository: TripInfoRepository,     ): TripInfoInteractor      @SecondTripQualifier     abstract fun provideTripInfoInteractorSecond(         @SecondTripQualifier         ordersRepository: CurrentOrderRepository,         @SecondTripQualifier         tripInfoRepository: TripInfoRepository,     ): TripInfoInteractor      @MainTripQualifier     abstract fun provideMapItemsInteractorMain(         @MainTripQualifier         ordersRepository: CurrentOrderRepository,         @MainTripQualifier         tripInfoRepository: TripInfoRepository,     ): MapItemsInteractor      @SecondTripQualifier     abstract fun provideMapItemsInteractorSecond(         @SecondTripQualifier         ordersRepository: CurrentOrderRepository,         @SecondTripQualifier         tripInfoRepository: TripInfoRepository,     ): MapItemsInteractor }

Что-ж, с таким решением не один месяц можно прожить, но что дальше, ведь такое решение совсем не масштабируемое. Для нескольких заказов не нагенерируешь таких квалификаторов в проекте. Что если вообще этих заказов может быть неограниченное кол-во.

Hidden text

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

@Override public void inject(MapScreen mapScreen) {     mapScreen.setViewModelMain(new ProvideBuilder<MapViewModel>((_lc0) -> {         TripInfoInMemory _lc1 = data().provideTripInfoInMemoryMain();         TripInfoApi _lc3 = data().provideTripInfoApi();         CurrentOrderInMemory _lc5 = data().provideCurrentOrderInMemoryMain();         CurrentOrderApi _lc7 = data().provideCurrentOrderApi();         TripInfoRepository _lc9 = repository().provideTripInfoRepositoryMain(_lc3, _lc1);         Ref<List<TripInfoRepository>> _lc10 = () -> new ProvideBuilder<TripInfoRepository>((_lc21) -> {             _lc21.add(repository().provideTripInfoRepositoryMain(_lc3, _lc1));         }).all();         CurrentOrderRepository _lc11 = repository().provideCurrentOrderRepositoryMain(_lc7, _lc5);         Ref<List<CurrentOrderRepository>> _lc12 = () -> new ProvideBuilder<CurrentOrderRepository>((_lc22) -> {             _lc22.add(repository().provideCurrentOrderRepositoryMain(_lc7, _lc5));         }).all();         Ref<List<MapItemsInteractor>> _lc14 = () -> new ProvideBuilder<MapItemsInteractor>((_lc23) -> {             _lc23.add(intractors().provideMapItemsInteractorMain(_lc11, ListUtils.first(NullGet.let(_lc10, Ref::get))));         }).all();         MapViewModel _lc15 = viewmodels().provideMapViewModelMain(ListUtils.first(NullGet.let(_lc14, Ref::get)));         _lc0.add(_lc15);     }).first());     mapScreen.setViewModelSecond(new ProvideBuilder<MapViewModel>((_lc25) -> {         Ref<List<TripInfoInMemory>> _lc27 = () -> new ProvideBuilder<TripInfoInMemory>((_lc42) -> {             _lc42.add(data().provideTripInfoInMemorySecond());         }).all();         TripInfoApi _lc28 = data().provideTripInfoApi();         Ref<List<CurrentOrderInMemory>> _lc31 = () -> new ProvideBuilder<CurrentOrderInMemory>((_lc44) -> {             _lc44.add(data().provideCurrentOrderInMemorySecond());         }).all();         CurrentOrderApi _lc32 = data().provideCurrentOrderApi();         TripInfoRepository _lc34 = repository().provideTripInfoRepositorySecond(_lc28, ListUtils.first(NullGet.let(_lc27, Ref::get)));         Ref<List<TripInfoRepository>> _lc35 = () -> new ProvideBuilder<TripInfoRepository>((_lc46) -> {             _lc46.add(repository().provideTripInfoRepositorySecond(_lc28, ListUtils.first(NullGet.let(_lc27, Ref::get))));         }).all();         CurrentOrderRepository _lc36 = repository().provideCurrentOrderRepositorySecond(_lc32, ListUtils.first(NullGet.let(_lc31, Ref::get)));         Ref<List<CurrentOrderRepository>> _lc37 = () -> new ProvideBuilder<CurrentOrderRepository>((_lc47) -> {             _lc47.add(repository().provideCurrentOrderRepositorySecond(_lc32, ListUtils.first(NullGet.let(_lc31, Ref::get))));         }).all();         Ref<List<MapItemsInteractor>> _lc39 = () -> new ProvideBuilder<MapItemsInteractor>((_lc48) -> {             _lc48.add(intractors().provideMapItemsInteractorSecond(_lc36, ListUtils.first(NullGet.let(_lc35, Ref::get))));         }).all();         MapViewModel _lc40 = viewmodels().provideMapViewModelSecond(ListUtils.first(NullGet.let(_lc39, Ref::get)));         _lc25.add(_lc40);     }).first()); }

Тысяча готова. И Еще на подходе

Мы в наших приложениях привыкли использовать компоненты в изолированных скоупах, ограниченных в рамках экранов, фрагментов, Activity или View. Для них можно делить, переносить и перетасовывать все новые компоненты, копировать множество архитектурных объектов сколько нужно. Но вот переиспользование таких компонентов в рамках локальных синглтонов становиться затруднительным. В одном компоненте DI просто так нельзя создавать, переиспользовать несколько экземпляров одного класса. Остается использовать независимые DI компоненты и использовать их через какой-нибудь менеджер этих самых компонентов. Немного выглядит как DI в DI.

В stone же можно использовать идентификаторы компонентов.

data class TripId(     val tripId: String, )  @Component(     identifiers = [         TripId::class     ] ) interface AppComponent {     // some code  }

Идентификаторы позволяют теперь дублировать объекты в одном скоупе и обращаться к ним по идентификаторам.

@Module abstract class InteractorsModule {      abstract fun provideTripInfoInteractor(         tripId: TripId,         ordersRepository: CurrentOrderRepository,         tripInfoRepository: TripInfoRepository,     ): TripInfoInteractor       abstract fun provideMapItemsInteractor(         tripId: TripId,         ordersRepository: CurrentOrderRepository,         tripInfoRepository: TripInfoRepository,     ): MapItemsInteractor  }
Идентификаторы передаются также как и зависимости. Могут быть аргументами к конструкторам

Идентификаторы передаются также как и зависимости. Могут быть аргументами к конструкторам

А все использование сводится к указанию нужного идентификатора на месте Inject’а объекта или его использования.

class MapScreen {      @Inject     lateinit var viewModel: MapViewModel      init {         DI.inject(mapScreen = this, tripId = TripId(argument.tripIdString))     }  }

DI компонент может легко отличить аргументы провайдинга как зависимости или идентификаторы. И предоставлять все или отдельные зависимости по идентификатору.

Hidden text

DI подставляет идентификаторы по всей цепочке раскручивания зависимостей

@Override public void inject(MapScreen mapScreen, TripId tripId) {     mapScreen.setViewModel(new ProvideBuilder<MapViewModel>((_lc0) -> {         TripInfoInMemory _lc1 = data().provideTripInfoInMemory(tripId);         TripInfoApi _lc3 = data().provideTripInfoApi();         CurrentOrderApi _lc5 = data().provideCurrentOrderApi();         Ref<List<CurrentOrderInMemory>> _lc8 = () -> new ProvideBuilder<CurrentOrderInMemory>((_lc20) -> {             _lc20.add(data().provideCurrentOrderInMemory(tripId));         }).all();         Ref<List<TripInfoRepository>> _lc10 = () -> new ProvideBuilder<TripInfoRepository>((_lc21) -> {             _lc21.add(repository().provideTripInfoRepository(tripId, _lc3, _lc1));         }).all();         CurrentOrderRepository _lc11 = repository().provideCurrentOrderRepository(tripId, _lc5, ListUtils.first(NullGet.let(_lc8, Ref::get)));         Ref<List<MapItemsInteractor>> _lc14 = () -> new ProvideBuilder<MapItemsInteractor>((_lc23) -> {             _lc23.add(intractors().provideMapItemsInteractor(tripId, _lc11, ListUtils.first(NullGet.let(_lc10, Ref::get))));         }).all();         MapViewModel _lc15 = viewmodels().provideMapViewModel(tripId, ListUtils.first(NullGet.let(_lc14, Ref::get)));         _lc0.add(_lc15);     }).first()); }

Сухой остаток

Одной из идей Stone — одна фабрика на все. И все было продумано, для того, чтобы избавиться от всяких фабрик и менеджеров-провайдеров viewModel’ей. А размытые скоупы в априори были призваны использовать минимальное кол-во DI скоупов на весь проект.

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

Заходите на wiki проекта и знакомьтесь с еще более продвинутыми фичами библиотеки.


ссылка на оригинал статьи https://habr.com/ru/articles/781472/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *