Как бороться с цикличными зависимостями
Что проверяют
Различаете ли вы цикл в графе объектов и цикл между модулями и умеете ли их разрывать, не пряча проблему. Следом обычно спрашивают, что скажет Dagger или Hilt на такой граф и как организовать навигацию между feature-модулями.
Как отвечать
Цикл это симптом: две сущности делят ответственность, которую надо разнести. Поэтому сначала выясните, какая из зависимостей лишняя.
Где цикл проявляется:
- граф DI: Dagger и Hilt падают на компиляции с ошибкой о dependency cycle, Koin узнает об этом только в рантайме при создании объекта;
- Gradle-модули:
:feature:profileзависит от:feature:chatи наоборот. Gradle такое не соберёт, и это сигнал, что границы модулей проведены неверно.
Способы разрыва, от лучших к худшим:
- вынести общее: логику, нужную обоим, переносят в третий класс или модуль
:core:*, от которого зависят оба; - разделение на api и impl: интерфейс лежит в лёгком модуле
:feature:chat:api, реализация в:feature:chat:impl, а связывает всё:app. Feature-модули зависят только от чужихapi; - события вместо обратной ссылки: нижний слой не зовёт верхний, а отдаёт
Flowили колбэк, на который тот подписан; Provider<T>илиLazy<T>: откладывают создание и формально разрывают цикл в Dagger. Это пластырь, связность остаётся.
Навигация между фичами идёт через интерфейсы навигатора в общем модуле или через маршруты Navigation, а не через прямой импорт экрана соседа. Удерживать границы помогают проверка графа модулей в CI и ArchUnit с правилом beFreeOfCycles() для пакетов.
Где спотыкаются
Предлагают завести IUserRepository и IUserService и считают вопрос закрытым. Интерфейс меняет тип зависимости, но объекты по-прежнему ссылаются друг на друга, и Dagger сообщит о том же цикле.
Не замечают нарушения слоёв: репозиторий, которому понадобился сервис доменного слоя, уже смотрит не в ту сторону. Правильнее убрать эту зависимость, а не прятать её за интерфейсом.
Лечат цикл через lateinit и ручную установку сеттером. Код компилируется, но порядок инициализации держится на удаче, и падение переезжает в рантайм.