Использование context в UseCase?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Использование 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 и улучшает тестируемость.