← Назад к вопросам

Может ли Context располагаться в UseCase?

3.0 Senior🔥 101 комментариев
#Архитектура и паттерны

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Краткий ответ

Нет, 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 во внешний слой.