Может ли Context располагаться в UseCase?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Краткий ответ
Нет, Context не должен располагаться в UseCase (или в любом другом классе доменного слоя). Это прямое нарушение принципов чистой архитектуры (Clean Architecture) и принципа единственной ответственности (Single Responsibility Principle). UseCase должен быть агностиком к платформе Android и не иметь зависимостей от фреймворка.
Детальное объяснение
Основная идея UseCase (или Интерактора) в чистой архитектуре — инкапсулировать конкретное бизнес-правило. Это центр вашего приложения, который не должен ничего знать о том, как данные представлены пользователю (UI), откуда они приходят (Data Layer) или в какой операционной системе работает приложение.
Контекст (android.content.Context) — это специфичный для Android фреймворк-зависимый класс. Его основное назначение:
- Доступ к ресурсам приложения (строки, цвета, drawable).
- Запуск
Activity,Service,BroadcastReceiver. - Работа с системными сервисами (Location, Notification, SharedPreferences).
- Доступ к файлам и базам данных приложения.
1. Нарушение архитектурных принципов
Размещение Context в UseCase немедленно привязывает ваш бизнес-слой к инфраструктурному слою и платформе Android. Это делает невозможным:
- Тестирование UseCase в изоляции (юнит-тесты) без подмены или создания
Context. - Переиспользование бизнес-логики в других проектах (например, в Kotlin Multiplatform модуле).
- Соблюдение направленности зависимостей (Dependency Rule), которая в чистой архитектуре гласит: зависимости могут направляться только внутрь, от внешних механизмов к внутренним бизнес-правилам.
Context— это внешний механизм.
2. Принцип единственной ответственности (SRP)
UseCase должен отвечать только за выполнение одной бизнес-операции. Если он начинает работать с Context, он берет на себя дополнительные, несвязанные обязанности (доступ к ресурсам, управление компонентами), что приводит к раздуванию класса и сложности поддержки.
3. Проблемы с жизненным циклом и утечками памяти
Context имеет жизненный цикл. Передача Activity Context в долгоживущий UseCase (например, синглтон или объект в графе зависимостей) — прямой путь к утечке памяти (memory leak), так как Activity не сможет быть собрана сборщиком мусора. UseCase не должен управлять или хранить ссылки на объекты с коротким жизненным циклом.
Правильные альтернативы
Вместо того чтобы передавать Context в UseCase, необходимо правильно разделить ответственность.
Вариант 1: Получение данных в Data Layer
Основная потребность в Context внутри бизнес-логики — часто это необходимость получить строки, проверить состояние сети или доступ к файлам. Эту задачу должен решать репозиторий (Repository) в Data Layer.
Неправильно:
// UseCase в Domain Layer - НЕПРАВИЛЬНО!
class ShowNotificationUseCase(private val context: Context) {
suspend operator fun invoke(message: String) {
// Создание нотификации - это не бизнес-логика!
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
.setContentTitle("Title")
.setContentText(message)
.build()
// ...
}
}
Правильно:
// Domain Layer
interface NotificationService { // Интерфейс объявлен в Domain
suspend fun showMessageNotification(message: String)
}
// Data Layer (или Infrastructure Layer в зависимости от подхода)
class AndroidNotificationService(
private val context: Context // Context инжектируется здесь
) : NotificationService {
override suspend fun showMessageNotification(message: String) {
// Вся работа с Android-специфичным кодом здесь
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
.setContentTitle("Title")
.setContentText(message)
.build()
// ...
}
}
// UseCase теперь чистый, зависит только от абстракции
class ShowMessageUseCase(
private val notificationService: NotificationService
) {
suspend operator fun invoke(message: String) {
// Только бизнес-логика: валидация message, логирование, etc.
if (message.isNotBlank()) {
notificationService.showMessageNotification(message)
}
}
}
Вариант 2: Передача простых параметров
Если UseCase нужен какой-то ресурс (например, строка для форматирования), этот ресурс должен быть получен заранее (например, во ViewModel или Presenter) и передан в UseCase в виде простого типа (String, Int).
// ViewModel/Presenter в Presentation Layer
class MyViewModel(
private val formatBalanceUseCase: FormatBalanceUseCase,
private val resources: Resources // Context получен и использован здесь
) : ViewModel() {
fun formatUserBalance(amount: Double) {
val currencySymbol = resources.getString(R.string.currency)
val formatted = formatBalanceUseCase(amount, currencySymbol) // Простые параметры
_balanceText.value = formatted
}
}
// UseCase в Domain Layer
class FormatBalanceUseCase {
operator fun invoke(amount: Double, currencySymbol: String): String {
// Чистая бизнес-логика форматирования
return if (amount >= 0) "$amount $currencySymbol" else "Долг: ${-amount} $currencySymbol"
}
}
Итог
- UseCase — это часть Domain Layer, которая должна быть чистой от зависимостей фреймворка и независимой от платформы.
- Context — это инфраструктурная деталь Android, которая принадлежит Data Layer или Presentation Layer.
- Правильное разделение позволяет создавать стабильный, тестируемый и сопровождаемый код, защищая ядро бизнес-логики приложения от изменений во внешних слоях. Если вам кажется, что UseCase нужен
Context— это сигнал о нарушении архитектурных границ и необходимости рефакторинга, обычно путем введения корректного интерфейса и переноса работы сContextво внешний слой.