Вопрос «какую архитектуру вы используете и почему» звучит на собесах iOS, Android, Flutter и React Native. Важно не перечислить буквы, а объяснить, какую проблему решает каждый паттерн и чем вы платите.
Проблема: толстый экран
Без архитектуры весь код экрана живёт в одном классе: UIViewController, Activity/Fragment, StatefulWidget, компонент React. Там же сеть, валидация, форматирование и навигация. Такой экран трудно читать, нельзя протестировать без UI и страшно менять. Все паттерны ниже отвечают на вопрос: куда вынести логику из view.
MVC
Модель (данные), View (отображение), Controller (связывает их). В iOS UIViewController одновременно управляет view и жизненным циклом, поэтому на практике контроллер разрастается: отсюда шутка «Massive View Controller». MVC нормален для маленьких экранов, но не даёт границ.
MVP
Presenter получает события от пассивной view и командует ей через интерфейс:
interface ProfileView { fun showLoading(); fun showProfile(p: Profile); fun showError(m: String) }
class ProfilePresenter(private val repo: ProfileRepository) {
var view: ProfileView? = null
suspend fun load() { view?.showLoading(); ... }
}
Плюс: логика тестируется с фейковой view. Минус: двусторонняя связь, много методов интерфейса, ручное управление ссылкой на view.
MVVM
View model хранит состояние экрана и не знает о view. View наблюдает состояние и отправляет действия:
class ProfileViewModel(private val repo: ProfileRepository) : ViewModel() {
private val _state = MutableStateFlow<ProfileState>(ProfileState.Loading)
val state: StateFlow<ProfileState> = _state
fun refresh() { viewModelScope.launch { _state.value = load() } }
}
В Swift то же через @Observable, во Flutter через ChangeNotifier или Cubit, в React Native через хуки и сторы. MVVM естественно ложится на декларативные UI: SwiftUI, Compose, Flutter. Это самый частый выбор сегодня.
MVI и однонаправленный поток
Экран описывается одним неизменяемым состоянием. Пользователь отправляет намерения (intents), редьюсер из старого состояния и намерения получает новое, побочные эффекты (сеть, таймеры) описаны отдельно:
действие → обработка → новое состояние → отрисовка
Плюсы: состояние всегда одно значение, его легко логировать, воспроизводить и тестировать. Минус: шаблонный код. Тот же принцип в TCA (iOS), BLoC (Flutter), Redux (React Native), Orbit и MVIKotlin (Android). На практике «MVVM с одним состоянием и UDF» почти неотличим от лёгкого MVI.
VIPER
Экран делится на View, Interactor (бизнес-логика), Presenter (подготовка данных для view), Entity (модели), Router (сборка и навигация). Даёт жёсткие границы для больших команд на UIKit, но каждый экран это 5+ файлов, и для простых экранов это избыточно.
Моделирование состояния
Плохо:
var isLoading = false
var error: String?
var items: [Item]?
Флаги допускают невозможные комбинации (загрузка, ошибка и данные одновременно). Лучше закрытая иерархия:
enum ScreenState {
case loading
case content([Item], isRefreshing: Bool)
case empty
case error(String)
}
Одноразовые события
Тост, переход, вибрация должны произойти один раз. Если хранить их флагом в состоянии, после поворота экрана или повторной подписки событие повторится. Варианты: событие как часть состояния с подтверждением обработки от view или отдельный канал (с риском потери, если подписчика нет). Навигацию лучше отдавать координатору или роутеру напрямую.
Как выбирать
- Маленькое приложение, декларативный UI: MVVM с одним состоянием на экран.
- Сложные экраны с множеством событий и эффектов: MVI или фреймворк вроде TCA/BLoC.
- Большая команда на UIKit с требованием жёстких границ: VIPER или его облегчённые варианты.
- В любом случае: один подход на проект, навигация и данные вне паттерна экрана.
Частые ошибки
- View model держит ссылку на view или контекст Activity.
- Набор флагов вместо одного состояния.
- Навигация внутри экранов, которая мешает переиспользованию.
- Разные паттерны на соседних экранах без причины.
Что спросят на собесе
- Чем отличаются MVC, MVP, MVVM, MVI и VIPER?
- Почему MVC в iOS называют Massive View Controller?
- Как вы моделируете состояние и одноразовые события?
- Какой паттерн вы выберете для нового проекта и почему?
- Найдите проблемы в коде view model.