site_logo

MV*-паттерны в разработке веб-приложения

Инструменты разработки
Интерфейс

Обновлено: 27 сентября 2024

    Код сложного приложения, написанного без 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-Controller
    Схема Model-View-Controller

    В данном паттерне мы видим три основных элемента:

    • Model — данные приложения с логикой их получения и сохранения. Чаще всего модель оперирует данными из базы данных или результатами работы веб-сервисов. Данные либо сразу выводятся на экран, либо адаптируются.
    • View — визуальный интерфейс, отрисовка кнопок, надписей, полей ввода и других элементов форм. Может следить за Model и отображать данные из неё.
    • Controller — следит за действиями пользователя (кнопки клавиатуры или движения мышкой), решает, что с ними делать, и обновляет Model и View.

    Принцип работы паттерна MVC опишем так. Controller обрабатывает действия пользователя — клики мышкой, нажатие клавиш клавиатуры или входящие http-запросы. Обработанные изменения Controller передаёт в Model и отрисовывает на View (пассивный режим), или в модель попадают изменения напрямую из View (активный режим). Главная задача View — отобразить данные из Model с помощью Controller.

    Model-View-Presenter

    Развитие визуального программирования и виджетов упразднило отрисовку отдельных элементов View, таким образом, и отдельный класс Controller стал не нужен. Элементы сами знают, какие действия с ними совершает пользователь. Но отделить логику приложения от данных всё равно необходимо. Так в паттерне произошла замена — вместо Controller появился Presenter.

    Схема Model-View-Presenter
    Схема Model-View-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.

    Схема Model-View-ViewModel
    Схема Model-View-ViewModel

    Здесь нет прямого общения между ViewModel и View, оно происходит посредством команд (binding), состоящих из свойств и методов. Так можно связать любые View и ViewModel, главное, чтобы имелись нужные свойства. XAML binding позволяет также связывать с View не только данные, но и действия. Мы задаём объект в виде свойства Model и декларативно связываем его с соответствующим свойством во View. На выходе получаем отдельный объект, который содержит и данные, и поведение, независимый от View. ViewModel — совмещение Model и Controller.

    Главные преимущества MVVM в лёгком проектировании интерфейсов, независимом тестировании и сокращении кода для View.

    Оптимизация и производительность

    Если мы используем MV*-паттерны для разделения кода View, Model и Controller, то в высоконагруженных системах дополнительное преимущество можно получить, разделив эти элементы по разным вычислительным единицам.

    1. View выносим на устройство клиента (ноутбук, ПК, смартфон). За ускорение отвечает технология одностраничного приложения SPA. Сложные расчёты выполняются на конечном оборудовании пользователя, тем самым снижая нагрузку на backend-сервера.
    2. Controller (Presenter, ViewModel) выносятся на отдельный backend-сервер, который обрабатывает логику.
    3. 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*-паттернов.