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

Использование context в UseCase?

1.7 Middle🔥 181 комментариев
#Архитектура и паттерны

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

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

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

Использование Context в UseCase

В архитектурных паттернах, таких как Clean Architecture или VIPER, UseCase (или Interactor) отвечает за бизнес-логику. Он получает данные из репозиториев, выполняет вычисления и возвращает результат Presentation слою (Presenter, ViewModel). Вопрос о передаче context (часто NSManagedObjectContext из Core Data или контекст выполнения) в UseCase возникает при работе с персистентностью или зависимостями.

Основные подходы и практики

1. Инъекция зависимости через параметр метода UseCase

UseCase может принимать context как параметр в своем исполнительном методе. Это делает UseCase более гибким и тестируемым, поскольку context можно заменять (например, на mock-контекст в тестах).

protocol FetchItemsUseCase {
    func execute(context: NSManagedObjectContext) -> [Item]
}

class DefaultFetchItemsUseCase: FetchItemsUseCase {
    private let repository: ItemRepository
    
    init(repository: ItemRepository) {
        self.repository = repository
    }
    
    func execute(context: NSManagedObjectContext) -> [Item] {
        return repository.fetchItems(context: context)
    }
}

Преимущества:

  • Отсутствие жесткой зависимости UseCase от конкретного контекста.
  • Легкость мокирования в unit-тестах.

Недостатки:

  • Каждый вызов требует передачи context, что может быть избыточно.

2. Инъекция зависимости через конструктор UseCase

Context внедряется как зависимость при создании UseCase. Это подходит, если UseCase всегда работает с одним контекстом (например, основным viewContext Core Data).

class UpdateItemUseCase {
    private let repository: ItemRepository
    private let context: NSManagedObjectContext
    
    init(repository: ItemRepository, context: NSManagedObjectContext) {
        self.repository = repository
        self.context = context
    }
    
    func execute(itemId: UUID, newTitle: String) {
        repository.updateItem(context: context, itemId: itemId, newTitle: newTitle)
    }
}

Преимущества:

  • Контекст фиксирован, что упрощает использование.
  • Меньше параметров в методе execute.

Недостатки:

  • UseCase становится менее гибким для разных контекстов (например, background-контекст).
  • Тесты требуют создания mock-контекста.

3. Использование абстракций над Context

Чтобы избежать прямой зависимости от NSManagedObjectContext, можно создать абстракцию (например, DataStorageContext). Это соответствует принципу Dependency Inversion.

protocol DataStorageContext {
    func fetch<T>(request: FetchRequest<T>) -> [T]
    func save(entity: Any)
}

class CoreDataContext: DataStorageContext {
    private let nsContext: NSManagedObjectContext
    
    init(nsContext: NSManagedObjectContext) {
        self.nsContext = nsContext
    }
    
    func fetch<T>(request: FetchRequest<T>) -> [T] {
        // Реализация через nsContext
    }
}

class FetchItemsUseCase {
    private let repository: ItemRepository
    private let context: DataStorageContext
    
    init(repository: ItemRepository, context: DataStorageContext) {
        self.repository = repository
        self.context = context
    }
    
    func execute() -> [Item] {
        return repository.fetchItems(context: context)
    }
}

Преимущества:

  • UseCase не зависит от конкретной технологии персистентности (Core Data, Realm, SQLite).
  • Максимальная гибкость и тестируемость.

Недостатки:

  • Дополнительный уровень абстракции, что увеличивает сложность.

Практические рекомендации

  • Для простых проектов с Core Data можно использовать инъекцию через конструктор, если контекст известен заранее.
  • Для больших и тестируемых систем лучше использовать абстракции или параметры в методах.
  • Важно соблюдать принцип единственной ответственности: UseCase не должен управлять жизненным циклом контекста (создавать, сохранять), он только использует его.
  • В многопоточных сценариях (например, использование performAndWait или perform) контекст должен быть правильно настроен (например, backgroundContext для тяжелых операций). UseCase может принимать контекст, предназначенный для определенной операции.

Пример использования в архитектуре

В Clean Architecture UseCase находится в Domain слое, который не должен знать о деталях инфраструктуры (Core Data). Поэтому контекст должен передаваться из внешних слоев (например, из Data слоя через Repository). Это может выглядеть так:

// В слое Domain (не зависит от Core Data)
protocol ItemRepository {
    func fetchItems(context: Any) -> [Item]
}

// В слое Data (знает о Core Data)
class CoreDataItemRepository: ItemRepository {
    func fetchItems(context: Any) -> [Item] {
        guard let nsContext = context as? NSManagedObjectContext else { return [] }
        // Запрос через nsContext
    }
}

// Использование в UseCase
let repository = CoreDataItemRepository()
let useCase = DefaultFetchItemsUseCase(repository: repository)
let items = useCase.execute(context: mainContext) // mainContext передается из Presentation/Data слоя

Вывод: Использование context в UseCase должно минимизировать зависимости и обеспечивать гибкость. Наиболее чистый подход — инъекция через параметр метода или абстракция над контекстом, что соответствует принципам SOLID и улучшает тестируемость.