Код сложного приложения, написанного без MV*-паттернов, тяжело тестировать, повторно использовать и поддерживать. Паттерны устраняют или ослабляют связь между View, Model и Controller, разделяют код и упрощают разработку. Мы расскажем о видах MV*-паттернов и их использовании в SimpleOne.
При разработке приложения SimpleOne мы тщательно прорабатываем дизайн пользовательского интерфейса (UI) и проектируем пользовательский опыт (UX), чтобы нашим клиентам было удобно и просто работать с платформой. Но интерфейс — это только верхушка айсберга, которую видит пользователь (User). За кнопками и полями скрывается код, который должен быть масштабируемым, сопровождаемым и надёжным. Для решения этих задач разработчики применяют MV*-паттерны, с помощью которых разделяют UI-код, логику и обработку данных.
UI без паттернов
Представим приложение, в котором есть форма с различными элементами и виджетами. В коде этой формы содержится как описание логики, так и код UI-элементов, также в нём могут присутствовать фрагменты для обработки данных. Для простого приложения поддержка такого принципа программирования не вызывает проблем. В любой момент можно найти взаимосвязи и внести изменения без нарушения целостности. Когда приложение становится сложнее, то поддержка интерфейса, написанного без паттернов, становится проблемой.
Проблема возникает из-за нарушения принципа единственной ответственности (single responsibility principle) — «У класса должна быть только одна причина для изменения». У нас в интерфейсе собраны и код UI (View), и логика (Controller), и обработка данных (Model), значит, причин для изменения несколько. При изменении кода одного компонента нам приходится вносить изменения и в другие. Так усложняется поддержка приложения, практически невозможно производить автоматизированное тестирование, и повторное использование кода сильно ограничено.
Поэтому удобно и правильно использовать паттерны — они разделяют UI-код (View), код логики (Presenter, Controller, ViewModel и другие) и код обработки данных (Model). Мы можем легко изменить логику, не меняя интерфейс, или внести изменения только в процедуры обработки данных. Каждый паттерн можно протестировать независимо от других и в дальнейшем использовать в других приложениях.
Самые часто используемые паттерны — это Model-View-Controller, Model-View-Presenter и Model-View-ViewModel. Рассмотрим, чем они отличаются и где применяются.
Model и View
Во всех трёх рассматриваемых паттернах есть два повторяющихся компонента. Они отличаются возможностями, но одинаковы по сути.
View — это визуальный интерфейс (UI). Он может состоять как из отдельных элементов, так и из виджетов. Примером View является код создания формы в MFC и WinForms, html в ASP.NET, XAML в WPF и Silverlight.
Model — это данные приложения, которые отображаются с помощью интерфейса, а также процесс их получения и сохранения.
Model-View-Controller
Старейший паттерн, который был разработан в 1979 году для разработки приложений на Smalltalk. В то время ещё не было графической оболочки Windows с её стандартными элементами. Интерфейс отрисовывали вручную кодом. Однако уже тогда разделение кода отображения, логики и данных стало переворотом в разработке.
В данном паттерне мы видим три основных элемента:
- Model — данные приложения с логикой их получения и сохранения. Чаще всего модель оперирует данными из базы данных или результатами работы веб-сервисов. Данные либо сразу выводятся на экран, либо адаптируются.
- View — визуальный интерфейс, отрисовка кнопок, надписей, полей ввода и других элементов форм. Может следить за Model и отображать данные из неё.
- Controller — следит за действиями пользователя (кнопки клавиатуры или движения мышкой), решает, что с ними делать, и обновляет Model и View.
Принцип работы паттерна MVC опишем так. Controller обрабатывает действия пользователя — клики мышкой, нажатие клавиш клавиатуры или входящие http-запросы. Обработанные изменения Controller передаёт в Model и отрисовывает на View (пассивный режим), или в модель попадают изменения напрямую из View (активный режим). Главная задача View — отобразить данные из Model с помощью Controller.
Model-View-Presenter
Развитие визуального программирования и виджетов упразднило отрисовку отдельных элементов View, таким образом, и отдельный класс Controller стал не нужен. Элементы сами знают, какие действия с ними совершает пользователь. Но отделить логику приложения от данных всё равно необходимо. Так в паттерне произошла замена — вместо Controller появился Presenter.
Если сравнивать с MVC, то функция Model не изменилась, View теперь сам обрабатывает действия пользователей (с помощью виджетов, например), а если это действие что-то меняет в логике интерфейса, то оно передаётся в Presenter.
Главная задача данного паттерна — отделить View от Controller, чтобы реализовать сменные View и иметь возможность их независимого тестирования.
Presenter как дирижёр — отвечает за синхронную работу Model и View. Если он получает уведомление от View о совершённом пользователем действии, то обновляет модель и синхронизирует изменения с View. Всё общение происходит через интерфейс, что и даёт их разделение.
У MVC есть две реализации: Passive View, где View ничего не знает о Model, а за получение информации из Model и обновление View отвечает Presenter, и Supervising Controller, где View знает о Model и сам связывает данные с отображением.
Model-View-ViewModel
Ключевое отличие этого паттерна от других — наличие связывания данных (databinding) в WPF и Silverlight.
Здесь нет прямого общения между ViewModel и View, оно происходит посредством команд (binding), состоящих из свойств и методов. Так можно связать любые View и ViewModel, главное, чтобы имелись нужные свойства. XAML binding позволяет также связывать с View не только данные, но и действия. Мы задаём объект в виде свойства Model и декларативно связываем его с соответствующим свойством во View. На выходе получаем отдельный объект, который содержит и данные, и поведение, независимый от View. ViewModel — совмещение Model и Controller.
Главные преимущества MVVM в лёгком проектировании интерфейсов, независимом тестировании и сокращении кода для View.
Оптимизация и производительность
Если мы используем MV*-паттерны для разделения кода View, Model и Controller, то в высоконагруженных системах дополнительное преимущество можно получить, разделив эти элементы по разным вычислительным единицам.
- View выносим на устройство клиента (ноутбук, ПК, смартфон). За ускорение отвечает технология одностраничного приложения SPA. Сложные расчёты выполняются на конечном оборудовании пользователя, тем самым снижая нагрузку на backend-сервера.
- Controller (Presenter, ViewModel) выносятся на отдельный backend-сервер, который обрабатывает логику.
- Model — самый объёмный и производительный элемент, поэтому для него необходим сервер с быстрой системой хранения данных, а лучше с возможностью кэширования «горячей» информации в оперативной памяти.
MVC в SimpleOne
При разработке ITSM-системы SimpleOne за основу взят паттерн Model-View-Controller, который существенным образом оптимизирован. Роль View выполняет одностраничное приложение SPA, которое работает полностью на клиентском устройстве и получает из Model только данные. А для работы Model мы используем подход DDD (Domain-Driven Design) — разделяем паттерн на слои-репозитории, через которые происходит обращение к данным.
В традиционной MVC код Model разработан под определённую БД и её синтаксис, например PostgreSQL. Используя систему репозиториев, мы можем переключать слои и подключать любую другую базу данных, не внося изменений в код приложения.
Слои позволяют не только работать с репозиториями как с коннекторами к БД, но и расщеплять на отдельные сущности любые классы. Например, если в обычном MVC Model класса User содержит несколько тысяч строк кода и отвечает за все операции, связанные с пользователем (поиск, удаление, отправка сообщений и другие), то в SimpleOne мы разделяем её на несколько отдельных слоев: класс описания пользователя, репозиторий пользователя для работы с БД, классы различных сервисов, связанных с пользователем, и другие. Получаем каркас MVC, но более глубоко проработанный, позволяющий проводить подмены, выполнять запросы значительно быстрее и более удобный с точки зрения обслуживания.
Заключение
Паттерны — это не жёсткие парадигмы, которые надо соблюдать для создания идеальной организации кода. Они решают очень важные задачи — ослабление (устранение) связей между View, Model и Controller и уменьшение сложности разработки пользовательских интерфейсов. Реализация такого подхода возможна, если понять суть взаимосвязей и искать возможность их устранения в каждом конкретном проекте. ESM-система SimpleOne — сложная и высоконагруженная с точки зрения разработки платформа, но при этом она обеспечивает понятный пользователям интерфейс и способна выдерживать большие нагрузки. Такая эффективность — это результат использования современных технологий и методов разработки, в том числе применения MV*-паттернов.