Слой представления
Как разделить данные, экран и ввод пользователя: MVC, MVP, MVVM
Когда экран, данные и обработка пользовательского ввода живут в одном классе, любое изменение дизайна рискует сломать бизнес-логику, а любой тест бизнес-правил тянет за собой весь UI. Презентационный слой - это семейство паттернов, решающих ровно эту проблему: они разводят три ответственности так, чтобы каждую можно было менять и тестировать отдельно.
MVC, MVP и MVVM - три классических паттерна этого семейства, и все три ведут родословную к одной и той же идее Трюгве Реенскауга 1979 года: данные (Model) не должны знать об экране, а экран должен лишь отображать данные. Разница между тремя паттернами - не в этом общем правиле, а в двух конкретных вопросах.
- Кто общается с View напрямую: в MVC это Controller, обычно без всякой абстракции между ним и конкретным виджетом. В MVP - Presenter, но уже через абстрактный интерфейс View, который легко подменить тестовой заглушкой.
- Как View узнаёт об изменениях: явный вызов метода отрисовки (Passive View), подписка через паттерн Observer, или автоматическое двустороннее связывание (data binding) в MVVM, где ViewModel вообще не знает о существовании View.
Универсально лучшего варианта нет - выбор паттерна определяется тем, какие средства связывания данных и экрана предоставляет платформа.
- MVC - естественный выбор для серверных веб-фреймворков (Rails, Django, Spring MVC): Controller создаётся заново на каждый HTTP-запрос, долгоживущее состояние экрана там и не нужно.
- MVP - когда Presenter обязан тестироваться юнит-тестами без реального UI: абстрактный интерфейс View позволяет подменить экран моком и проверить логику в изоляции.
- MVVM - когда платформа сама умеет data binding (WPF/XAML, Angular, Vue, SwiftUI): ViewModel просто выставляет наблюдаемые свойства, а View подписывается на них декларативно, без ручного кода синхронизации.
Практическое правило: если платформа даёт готовый механизм связывания - берите MVVM. Если нужна тестируемость логики без готового биндинга - MVP. Если экран живёт ровно один запрос-ответ - классический MVC.
Связывание данных (data binding) - механизм, которым View узнаёт об изменении Model или ViewModel. Он бывает разной степени автоматизации, и от выбора зависит, сколько кода придётся писать вручную.
- Одностороннее связывание (one-way binding): данные текут только от Model/ViewModel к View - типичная схема React или Vue при обычном рендере: состояние поменялось, компонент перерисовался, обратного пути нет.
- Двустороннее связывание (two-way binding): View может напрямую менять Model/ViewModel в ответ на ввод пользователя - классика Angular (
ngModel) и WPF, удобно для форм, но усложняет отслеживание источника изменения. - Явное уведомление (push через Observer): Model сама рассылает событие подписчикам при изменении - оригинальный механизм MVC в Smalltalk и
NSNotificationCenterв Cocoa AppKit. - Ручной вызов (Passive View): ни подписки, ни автоматического биндинга нет вообще - Controller или Presenter явно вызывает метод View после каждого изменения, и это самый простой вариант для мокирования в тестах.
Презентационные паттерны удобно сравнивать по нескольким независимым осям - это помогает быстро понять, чем один вариант отличается от другого, не читая полное описание каждого.
- По месту, где живёт решающая логика: Controller в MVC, Presenter в MVP, ViewModel в MVVM - в каждом паттерне это один и тот же по смыслу компонент под разным именем.
- По механизму уведомления View: ручной вызов (Passive View), подписка Observer, автоматический data binding - от самого явного к самому неявному.
- По тестируемости без реального UI: MVC сложнее всего - Controller обычно трогает View напрямую. MVP проще - интерфейс View подменяется моком. MVVM проще всего - ViewModel вообще не хранит ссылку на View.
- По накладным расходам: MVC добавляет меньше всего кода на маленьком экране, MVVM требует инфраструктуры для observable-свойств, но окупается на экранах с большим количеством полей и сложной валидацией.
MVC
MVC разделяет приложение на три взаимосвязанные части: Model хранит данные и бизнес-логику, View отображает их пользователю, а Controller принимает пользовательский ввод и решает, как на него отреагировать - так что экран, данные и логику ввода можно менять и тестировать по отдельности.
MVP
MVP развивает MVC и доводит View до полной пассивности: он не выполняет никакой логики и лишь передаёт действия пользователя в Presenter, а для обновления экрана предоставляет несколько простых методов. Presenter закреплён за своим View один к одному, работает с ним через интерфейс, меняет Model и готовит её данные к выводу. За счёт этого всю логику представления можно покрыть юнит-тестами, подставив вместо настоящего View заглушку.