Зачем юзкейсы закрывать интерфейсами
Что проверяют
Понимаете ли вы, какую зависимость на самом деле разворачивает интерфейс, или повторяете шаблон «на всё по интерфейсу». Следом обычно спрашивают, чем интерфейс юзкейса отличается от интерфейса репозитория и нужен ли он для тестов ViewModel.
Как отвечать
Начните с разделения. Интерфейс репозитория в Clean Architecture обязателен: он объявлен в domain, реализован в data, и domain не зависит от Retrofit и Room. С юзкейсом иначе: его потребитель, ViewModel, и так стоит слоем выше, инверсия тут не требуется. Поэтому интерфейс юзкейса оправдывают конкретными причинами:
- многомодульность: в фиче с разделением на
:feature:apiи:feature:implинтерфейс лежит в api, реализация в impl. Другие модули видят только контракт, и правка реализации не пересобирает всех потребителей; - фейки в тестах: ViewModel тестируется с простой
FakeGetProfileUseCase, которая отдаёт заданныйFlow, без моков. Документация Android по тестированию советует предпочитать фейки мокам; - несколько реализаций: разные варианты для флейворов, эксперимента или демо-режима;
- DI: в Hilt интерфейс связывается с реализацией через
@Binds, в Koin черезbind.
Честно назовите цену: пара GetProfileUseCase и GetProfileUseCaseImpl на каждый юзкейс удваивает число файлов. Если юзкейс тонкий и живёт в одном модуле, класс с operator fun invoke без интерфейса вполне нормален, а в тесте ViewModel можно взять настоящий юзкейс с фейковым репозиторием. Руководство Google по архитектуре называет domain-слой необязательным и интерфейсов для юзкейсов не требует.
Где спотыкаются
Говорят, что без интерфейса класс не замокать, потому что в Kotlin классы final. MockK мокает финальные классы, Mockito начиная с пятой версии тоже умеет это по умолчанию.
Заводят интерфейс на каждый юзкейс с единственной реализацией и не могут объяснить зачем. Интервьюер ждёт критерия, а не ритуала.
Путают, где нужна инверсия. Интерфейс юзкейса не спасёт архитектуру, если сам юзкейс зависит от конкретного класса репозитория из data.