В чем разница между Presenter и Interactor?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Разница между Presenter и Interactor в архитектуре VIPER/Clean Swift
В архитектурах VIPER и Clean Swift (VIP), которые следуют принципам Clean Architecture, Presenter и Interactor играют фундаментально разные роли, хотя оба являются посредниками между пользовательским интерфейсом и бизнес-логикой. Их различие — краеугольный камень разделения ответственности.
Основное назначение и ответственность
Interactor (Интерактор)
Это ядро бизнес-логики приложения. Его главная задача — выполнение конкретных юзкейсов (Use Cases). Interactor:
- Работает с сущностями (Entities) — чистыми моделями данных, не зависящими от фреймворков.
- Содержит бизнес-правила и логику обработки данных (валидация, вычисления, фильтрация).
- Не знает о существовании пользовательского интерфейса (UI). Он абстрагирован от UIKit/SwiftUI.
- Получает сырые или подготовленные данные от Gateways (например, NetworkService, DatabaseService) через протоколы.
- Результат своей работы передает Presenter в формате, удобном для бизнес-логики (обычно те же Entities или простые структуры).
Presenter (Презентер)
Это подготовщик данных для отображения. Его главная задача — преобразовывать данные из бизнес-моделей (от Interactor) в модели представления (View Models), понятные View.
- Работает с моделями представления (View Models) — структурами, оптимизированными для отображения (например, отформатированные строки дат, флаги для скрытия/показа UI-элементов).
- Содержит логику подготовки данных для UI, но не саму бизнес-логику.
- Знает о View, но общается с ней через протокол, что позволяет легко подменять реализацию (например, для тестов).
- Решает, что и как показать пользователю: "показать ошибку", "обновить список", "перейти к следующему экрану".
Сравнение на примере VIPER
Рассмотрим сценарий "Загрузка списка новостей".
// Entity: Чистая бизнес-модель
struct Article {
let id: Int
let title: String
let publicationDate: Date
let isRead: Bool
}
// View Model: Модель, готовящаяся для отображения
struct ArticleViewModel {
let title: String
let subtitle: String // "Опубликовано: 15 мая 2023"
let isHighlighted: Bool
}
Работа Interactor:
protocol ArticlesInteractorInput: AnyObject {
func fetchArticles()
}
protocol ArticlesInteractorOutput: AnyObject {
func didFetchArticles(_ articles: [Article])
func didFailToFetchArticles(with error: Error)
}
final class ArticlesInteractor: ArticlesInteractorInput {
weak var presenter: ArticlesInteractorOutput?
let service: ArticlesServiceProtocol // Протокол для абстракции над сетью/БД
func fetchArticles() {
service.loadArticles { [weak self] result in
switch result {
case .success(let articles):
// Бизнес-логика: например, помечаем статьи старше недели как прочитанные
let processedArticles = self?.applyBusinessRules(to: articles) ?? articles
self?.presenter?.didFetchArticles(processedArticles)
case .failure(let error):
self?.presenter?.didFailToFetchArticles(with: error)
}
}
}
private func applyBusinessRules(to articles: [Article]) -> [Article] {
let weekAgo = Calendar.current.date(byAdding: .day, value: -7, to: Date())!
return articles.map { article in
var modifiedArticle = article
if article.publicationDate < weekAgo {
modifiedArticle.isRead = true
}
return modifiedArticle
}
}
}
Работа Presenter:
protocol ArticlesViewInput: AnyObject {
func showArticles(_ viewModels: [ArticleViewModel])
func showError(message: String)
}
final class ArticlesPresenter: ArticlesInteractorOutput {
weak var view: ArticlesViewInput?
let dateFormatter: DateFormatter
init() {
dateFormatter = DateFormatter()
dateFormatter.dateStyle = .long
dateFormatter.timeStyle = .none
}
// Interactor передал бизнес-модели (Entities)
func didFetchArticles(_ articles: [Article]) {
// Преобразуем Entity во View Model: это ответственность Presenter
let viewModels = articles.map { article in
ArticleViewModel(
title: article.title,
subtitle: "Опубликовано: \(dateFormatter.string(from: article.publicationDate))",
isHighlighted: !article.isRead // Логика отображения на основе бизнес-состояния
)
}
view?.showArticles(viewModels)
}
func didFailToFetchArticles(with error: Error) {
// Подготавливаем сообщение об ошибке для пользователя
let userMessage = "Не удалось загрузить новости. Пожалуйста, проверьте соединение."
view?.showError(message: userMessage)
}
}
Ключевые различия в виде таблицы
| Критерий | Interactor | Presenter |
|---|---|---|
| Основная роль | Исполнитель бизнес-юзкейсов. | Преобразователь данных для UI. |
| Работает с | Entities (бизнес-модели), Protocols (для сервисов). | View Models, Entities (на вход), View Protocol (на выход). |
| Ответственность | Валидация, вычисления, применение бизнес-правил, работа с данными. | Форматирование, подготовка строк, определение состояния UI-элементов. |
| Знание о UI | Нет (полная абстракция). | Да (знает, что нужно показать, но не как это рендерить). |
| Зависимости | Сервисы (сеть, БД), другие Interactors. | Форматтеры, мапперы, Router (для навигации). |
| Тестируемость | Легко тестируется юнит-тестами, так как не зависит от фреймворков. | Легко тестируется юнит-тестами с моком View. |
Важный принцип взаимодействия
Поток данных обычно следует паттерну: View -> Presenter -> Interactor -> Presenter -> View
- View (например, ViewController) сообщает Presenter о действии пользователя (например,
viewDidLoad). - Presenter запрашивает у Interactor выполнение бизнес-логики (например,
fetchArticles). - Interactor выполняет работу, получая данные из сервисов, применяя правила.
- Interactor возвращает результат (Entities) Presenter.
- Presenter преобразует Entities в View Models и передает их View для отображения.
Итог: Interactor — это "мозг" бизнес-логики, независимый от платформы. Presenter — это "переводчик", который адаптирует результаты работы "мозга" для "лица" приложения (View). Их четкое разделение позволяет создавать гибкий, тестируемый и поддерживаемый код, где изменения в UI не затрагивают бизнес-правила, и наоборот.