mvp архитектура что это
Martin Fowler — GUI Architectures. Часть 4
Архитектура MVP впервые появилась в IBM и более явно проявилась в системе Taligent в 1990-ые годы. Достаточно подробно описана в работе Потеля (Potel). Идея была подхвачена разработчиками Dolphin Smalltalk. Как мы увидим позже, эти двe работы говорят не совсем об одном и том же, однако заложенная в них идея стала очень популярной. Будем ссылаться на эти два источника при описании архитектуры.
Для того, чтобы начать описывать MVP, я нахожу полезным подумать о существенных различиях между двумя столпами разработки UI. С одной стороны находятся «Формы и элементы», архитектура, являющаяся мэйнстримом UI-дизайна. С другой стороны — MVC и его производные. Модель «форм и элементов» предоставляет разработчику легко понимаемый дизайн и позволяет без особого труда проводить разделение между повторно используемыми элементами управления и бизнес-кодом приложения. Ей не хватает того, что в достатке есть у MVC — разделенного представления (Separated Presentation) и контекста программирования, обуславливаемого использованием доменной модели (Domain Model). Я воспринимаю MVP как объединение этих двух моделей, как попытку взять из них самое лучшее.
Согласно Potel, представление в MVP следует понимать как структуру элементов управления формы в модели «Форм и элементов», а любые разделения элемента управления на контроллер\представление следует убрать. Представление не должно содержать никакого поведения, определяющего, как элементу взаимодействовать с пользователем.
Это взаимодействие выносится в отдельный объект-представление (presenter). В представлении остаются основополагающие обработчики событий элементов управления, однако их назначением является передача управления в presenter.
Presenter должен определить, как ему реагировать на событие. Potel определяет это взаимодействие в терминах действий над моделью, которое происходит через систему команд и выборок. Кстати, можно взять на заметку этот подход — упаковка всех редакций модели в команду. Он позволит легко ввести поведение «Отменить/Повторить» (undo/redo).
Обратное обновление из модели в представление происходит через все ту же синхронизацию через обновитель (Observer Synchronization), которая используется в MVC.
Похожее описание определяется в работе Dolphin. В ней так же требуется наличие объекта presenter. Однако, в работе Dolphin не определяется система взаимодействия presenter с моделью. К тому же, там недвусмысленно намекается на то, что объект presenter должен иметь прямой доступ к представлению. Potel ничего не говорит о том, должен ли presenter иметь доступ к представлению или нет, но для Dolphin это поведение является очень важным, как раз из-за той проблемы в модели приложения (Application Model) с цветом отклонения, которую я описывал в предыдущем разделе. Как вы помните, оно решалось таким неуклюжим, с моей точки зрения, способом.
Одним из других ключей к пониманию MVP является осознание подхода, с которым presenter контролирует элементы в представлении. С одной стороны, Potel утверждает, что вся логика представления должна быть оставлена в представлении и presenter не должен определять отрисовку (render) модели. Господа Bower and McGlashan предлагают свое решение, которое я называю Supervising Controller. В этом решении почти вся логика представления так же определяется в представлении, однако presenter обладает возможностью влиять на эту логику в сложных сценариях. Т.е. некоторая часть логики представления может быть помещена в presenter.
К тому же, вы можете перенести полностью всю логику представления в presenter и получить то, что я называю Passive View. Изначально Passive View не являлось частью модели MVP. Эта модель появилась позже, тогда, когда люди научились решать проблемы тестирования. Я поговорю об этой модели позже, а сейчас скажу только то, что она является одним из видов MVP.
Перед тем, как определять отличие MVP от всего другого, что я описывал выше, я бы хотел сказать пару слов об упоминаемых мной работах. В них определяется разница MVP от MVC, но только не так, как ее вижу я. Potel утверждает, что контроллеры в MVC являются общими координаторами — с чем я не согласен. Dolphin много говорит о проблемах MVC, при этом под MVC они понимают модель приложения VisualWorks, а не тот классический MVC, про который говорил я. Я не виню их за это — в наше время информацию по классическому MVC не очень просто достать.
Теперь самое время рассказать об отличиях MVP от предыдущих архитектур (так как вижу их я):
Между presenter-ами MVP и контроллерами MVC наблюдается явное сходство. Presenter это всего лишь более свободная форма контроллера. В результате, много архитектур последуют пути MVP, но при этом будут использовать термин «контроллер», имея в виду presenter. Вообще говоря, использовать контроллер нужно тогда, когда решается вопрос со взаимодействием с пользовательским вводом на уровне элемента управления.
Рисунок 12: Диаграмма последовательности обновления текущего значения в модели MVP
Давайте посмотрим, как работает обновление текущего значения в примере с мороженым (рисунок 12). Начало диаграммы очень похоже на ту, что была в «формах и элементах» — пользователь вводит значение, текстовое поле посылает событие «текст изменен». Presenter слушает это событие и ловит его, после чего забирает у представления новое значение. Потом он обновляет доменный объект, который в свою очередь обозревается текстовым полем отклонения. Текстовое поле отклонения обновляет свое значение вычисленным, после чего ему назначается цвет. Цвет назначает presenter. Он читает категорию отклонения у замера и обновляет цвет у текстового поля (через прямую ссылку на элемент управления).
Подведем итоги модели MVP:
Паттерны разработки: MVC vs MVP vs MVVM vs MVI
От переводчика: данная статья является переработкой английской статьи по паттернам разработки. В процессе адаптации на русский немало пришлось изменить. Оригинал
Выбор между различными паттернами разработки, всегда сопровождается рядом споров и дискуссий, а разные взгляды разработчиков на это еще больше усложняют задачу. Существует ли решение этой идеологической проблемы? Давайте поговорим о MVC, MVP, MVVM и MVI прагматично. Давайте ответим на вопросы: “Почему?”, “Как найти консенсус?”
Вступление
Вопрос выбора между MVC, MVP, MVVM и MVI коснулся меня, когда я делал приложение для Warta Mobile вместе с моей командой. Нам было необходимо продвинуться от минимально жизнеспособного продукта к проверенному и полностью укомплектованному приложению, и мы знали, что необходимо будет ввести какую-либо архитектуру.
У многих есть непоколебимое мнение насчет различных паттернов разработки. Но когда вы рассматриваете архитектуру, такую как MVC, то, казалось бы, полностью стандартные и определенные элементы: модель (Model), представление (View) и контроллер (Controller), разные люди описывают по разному.
Трюгве Реенскауг (Trygve Reenskaug) — изобрел и определил MVC. Через 24 года после этого, он описал это не как архитектуру, а как набор реальных моделей, которые были основаны на идее MVC.
Я пришел к выводу, что поскольку каждый проект — уникален, то нет идеальной архитектуры.
Необходимо детально рассмотреть различные способы реализации, и подумать над преимуществами и недостатками каждой.
Чего мы хотим добиться?
Масштабируемость, сопровождаемость, надежность
Очевидно, что масштабируемость (scalability) — возможность расширять проект, реализовывать новые функции.
Сопровождаемость (maintainability) — можно определить как необходимость небольших, атомарных изменений после того, как все функции реализованы. Например, это может быть изменение цвета в пользовательском интерфейсе. Чем лучше сопровождаемость проекта, тем легче новым разработчикам поддерживать проект.
Надежность (reliability) — понятно, что никто не станет тратить нервы на нестабильные приложения!
Разделение отвественности, повторное использование кода, тестируемость
Важнейшим элементом здесь является разделение ответсвенности (Separation of Concerns): различные идеи должны быть разделены. Если мы хотим изменить что-то, мы не должны ходить по разным участкам кода.
Без разделения ответственности ни повторное использование кода (Code Reusability), ни тестируемость (Testability) практически невозможно реализовать.
Ключ в независимости, как заметил Uncle Bob в Clean Architecture. К примеру, если вы используете библиотеку для загрузки изображений, вы не захотите использовать другую библиотеку, для решения проблем, созданных первой! Независимость в архитектуре приложения — частично реализует масштабируемость и сопровождаемость.
Model View Controller
У архитектуры MVC есть два варианта: контроллер-супервизор (supervising controller) и пассивное представление (passive view).
В мобильной экосистеме — практически никогда не встречается реализация контроллера-супервизора.
Архитектуру MVC можно охарактеризовать двумя пунктами:
Диаграмма иллюстрирует идеологию паттерна. Здecь, представление определяет как слушателей, так и обратные вызовы; представление передает вход в контроллер.
Контроллер принимает входные данные, а представление — выходные, однако большое число операций происходит и между ними. Данная архитектура хорошо подходит только для небольших проектов.
Пассивное представление MVC
Главная идея пассивного представления MVC — это то, что представление полностью управляется контроллером. Помимо этого, код четко разделен на два уровня: бизнес логику и логику отображения:
Massive View Controller
Нельзя трактовать Активити как представление (view). Необходимо рассматривать его как слой отображения, а сам контроллер выносить в отдельный класс.
А чтобы уменьшить код контроллеров представлений, можно разделить представления или определить субпредставления (subviews) с их собственными контроллерами. Реализация MVC паттерна таким образом, позволяет легко разбивать код на модули.
Однако, при таком подходе появляются некоторые проблемы:
Решение этих проблем кроется за созданием абстрактного интерфейса для представления. Таким образом, презентер будет работать только с этой абстракцией, а не самим представлением. Тесты станут простыми, а проблемы решенными.
Все это — и есть главная идея MVP.
Model View Presenter
Данная архитектура облегчает unit-тестирование, презентер (presenter) прост для написание тестов, а также может многократно использоваться, потому что представление может реализовать несколько интерфейсов.
С точки зрения того, как лучше и корректней создавать интерфейсы, необходимо рассматривать MVP и MVC только как основные идеи, а не паттерны разработки.
Use cases
Создание use cases — это процесс выноса бизнес логики в отдельные классы, делая их частью модели. Они независимы от контроллера и каждый содержит в себе одно бизнес-правило. Это повышает возможность многократного использования, и упрощает написание тестов.
В примере на GitHub, в login controller, вынесен use case валидации и use case логина. Логин производит соединение с сетью. Если есть общие бизнес правила в других контроллерах или презентерах, можно будет переиспользовать эти use case’ы.
Привязывание представления (View Bindings)
В реализации MVP есть четыре линейный функции, которые ничего не делают, кроме небольших изменений в пользовательском интерфейсе. Можно избежать этого лишнего кода, использовав view binding.
Все способы биндинга можно найти здесь.
Здесь простой подход: легко тестировать, и еще легче представить элементы представления как параметры через интерфейс, а не функции.
Стоит отметить, что с точки зрения презентера — ничего не изменилось.
Model View View-Model
Существует другой способ биндинга: вместо привязывания представления к интерфейсу, мы привязываем элементы представления к параметрам view-модели — такая архитектура называется MVVM. В нашем примере, поля email, password, и разметка определены с помощью связываний. Когда мы меняем параметры в нашей модели, в разметку тоже вносятся изменения.
ViewModel’и просты для написания тестов, потому что они не требуют написания mock-объектов — потому что вы меняете свой собственный элемент, а потом проверяете как он изменился.
Model View Intent
Еще один элемент, который можно ввести в архитектуре, обычно называется MVI.
Если взять какой-либо элемент разметки, например кнопку, то можно сказать, что кнопка ничего не делает, кроме того, что производит какие-либо данные, в частности посылает сведения о том что она нажата или нет.
В библиотеке RxJava, то, что создает события — называется observable, то есть кнопка будет являться observable в парадигме реактивного программирования.
А вот TextView только отображает какой-либо текст и никаких данных не создает. В RxJava такие элементы, которые только принимают данные, называются consumer.
Также существуют элементы, которые делают и то и то, т. е. и принимают и отправляют информацию, например TextEdit. Такой элемент одновременно является и создателем (producer) и приемником (receiver), а в RxJava он называется subject.
При таком подходе — все есть поток, и каждый поток начинается с того момента, как какой-либо producer, начинает испускать информацию, а заканчивается на каком-либо receiver, который, в свою очередь, информацию принимает. Как результат, приложение можно рассматривать как потоки данных. Потоки данных — главная идея RxJava.
Заключение
Несмотря на то, что внедрение разделения ответсвенности требует усилий, это хороший способ повысить качество кода в целом, сделать его масштабируемым, легким в понимании и надежным.
Другие паттерны, такие как MVVM, удаление шаблонного кода, MVI могут еще сильнее улучшить масштабируемость, но сделают проект зависимым от RxJava.
Также, следует помнить, что можно выбрать всего лишь часть из этих элементов, и сконфигурировать для конечного приложения в зависимости от задач.
Что такое MVP и как это использовать
MVP — это паттерн, который используют многие разработчики, даже если не догадываются об этом. Может, вы один из них?
MVP — это паттерн программирования графических интерфейсов. В нём приложение делится на три компонента:
Как и другие подобные паттерны (MVC, MVVM), MVP позволяет ускорить разработку и разделить ответственность разных специалистов; приложение удобнее тестировать и поддерживать.
Чаще всего его используют разработчики мобильных приложений, однако он гораздо популярнее, чем может показаться. Дело в том, что этот паттерн активно применяется в вебе, хотя там его и называют MVC:
Пишет о программировании, в свободное время создает игры. Мечтает открыть свою студию и выпускать ламповые RPG.
Как работает MVP
На схеме выше видно, что приложение, созданное по принципам MVP, работает с помощью связи модели, вида и представителя. Происходит это так:
Основное отличие MVP и MVC в том, что в MVC обновлённая модель сама говорит виду, что нужно показать другие данные. Если же этого не происходит и приложению нужен посредник в виде представителя, то паттерн стоит называть MVP.
Всё это можно сравнить с работой издательства:
Конечно, это не точный алгоритм работы издательства, но для иллюстрации принципов MVP его достаточно.
Пример MVP-приложения
Так как MVP служит для упрощения разработки графических интерфейсов, рассмотреть его можно на примере WPF-приложения. В качестве вида будут выступать файлы MainWindow.xaml (разметка интерфейса) и MainWindow.xaml.cs (обработчик событий).
Начать можно с создания интерфейса авторизации:
Model-View-Presenter — компромисс и универсальный рецепт
Аббревиатуру MVP можно интерпретировать по разному, но в статье речь пойдет не о спорте.
В сети есть большое количество статей по архитектурным паттернам для iOS и Android разработчиков, и по MVP в частности. Иногда паттерн рассматривается в контексте обеих платформ. Кто-то выбирает данный паттерн для улучшения тестируемости своего кода, кто-то использует его в основном для разделения кода представления от модели. Также встречаются решения, которые используют MVP для унифицирования кода платформ, при условии что разработчики в компании владеют данными технологиями. Но общих слов и выводов иногда недостаточно для разработчика-прагматика. При проектировании коммерческих приложений неизбежно возникает множество деталей, которые общая архитектурная концепция не может раскрыть, и нельзя сказать, что есть единственное каноническое решение.
В статье я постараюсь описать ситуации, с которыми очень часто сталкиваются мобильные разработчики на реальных проектах, и когда действительно стоит задуматься о переходе на архитектурный паттерн более сложный чем “One UIViewController (Activity) to rule them all”. Или лучше сказать, когда нам это будет выгодно. Далее речь пойдет о компромиссе между временем и сложностью разработки в реалиях “обычных” проектов, которые в основном приходят на оценку и разработку.
Введение
Большинство мобильных проектов для нативных SDK сейчас являются клиент-серверными приложениями. В свою очередь такие приложения по сложности системы и времени жизни могут сильно отличаться. Есть приложения “однодневки” для работы в рамках одного мероприятия или ежегодного праздника, после которого они никому не нужны. Есть и серьезные проекты, требующие большого процента покрытия кода тестами, для обеспечения стабильной работы при внесении изменений разными участниками команды.
Какие проекты являются “обычными” можно определить по следующим критериям:
Сравнение MVP с паттернами по умолчанию
Рассмотрим самый простой паттерн, а именно iOS MVC с пассивной моделью, который является по сути жесткой связкой слоев представления и модели, где вся логика находится в UIViewController-е. Аналогичный подход получается и на Android-е, если вы пишите свои проекты по руководству из видеоуроков для начинающих или с большинства обучающих сайтов.
Если сравнить его с MVP, то основным отличием является класс Presenter, в который мы выносим логику обработки событий, форматирования данных и управления View. Под View мы будем понимать слой, включающий дочерние классы UIViewController, Activity или Fragment и их связку с классами всевозможных контролов. Таким образом мы “разгружаем” классы аналогичные UIViewController от тех обязанностей, которыми они не должны заниматься, оставляя внутри только код инициализации представлений и анимаций.
Как это часто бывает, в течение разработки первой версии приложения дополнительно добавляются функциональности, связанные с недостаточной проработкой ТЗ. Иногда изменения настолько радикальны, что приходится переписывать бОльшую часть уже проделанной работы. Наиболее часто встречается ситуация, когда просят добавить дополнительное поле к сущности и отобразить отформатированную информацию. Проследим какие классы мы изменим в проекте при добавление нового UI-элемента, отображающего отформатированную дату:
Очевидно, что мы усложнили систему введением паттерна MVP, так как количество классов, которые подверглись изменению при простом добавлении нового свойства сущности у нас возросло. Отдельно стоит отметить, что при реализации MVP создают отдельный интерфейс для View и пытаются сделать так, чтобы в Presenter-е находилось как можно меньше кода, связанного с платформой и не было прямых ссылок на конкретных наследников UIViewController, Activity или Fragment, потому что иначе будет соблазн написать код представления непосредственно в Presenter-е.
Для небольших и слабо подверженных изменениям проектов, подход без четкого отделения представления от бизнес логики наоборот является, на мой взгляд, лучшим, так как существенно сокращает время разработки. Небольшой проект обычно пишет один человек, а количество кода не составит труда другому участнику команды разобраться в течение нескольких часов с задачей и внести изменения, если это требуется.
В поиске компромисса
Встает логичный вопрос, а зачем же все усложнять?
Во-первых, для того, чтобы четко распределить ответственность между классами. Это особо актуально, если вы являетесь участником команды разработки. Важно понимать, что никто из программистов не мыслит одинаково. Например, код форматирования даты из примера в итоге может оказаться как в ячейке таблицы, так и в контроллере, так и в коде класса, отвечающего за маппинг данных, пришедших с сервера.
Таких деталей, помимо форматирования, в вашем проекте может встретиться немало. Можно привести более сложный пример с отображением в элементе списка составной модели представления, которая собирается по частям из закэшированных данных. Всю работу по форматированию и подготовку модели для представления мы переносим в Presenter, поэтому неоднозначности в том, где форматировать данные, не остается.
Не стоит путать модель представления в данной статье с ViewModel из MVVM, название лишь указывает на сущность, хранящую отформатированную и готовую к показу во View информацию.
При использовании обычного подхода мы, скорее всего, разместили бы код составления модели для ячейки в наследнике UIViewController-е или Activity (Fragment-е), что повлекло за собой увеличение кода в классе, в котором и так намешано немало, связанного с функционированием представления.
Написание “полезных” интеграционных тестов также становится возможным с вводом MVP, где основным объектом тестирования выступает Presenter. Конечно, используя только юнит-тестирование, можно протестировать используемые в приложении компоненты, которые являются God-объектами и наследниками UIViewController или Activity (Fragment), но это будет не вполне эффективно.
Рассмотрим момент, связанный с расширением проекта. Дополнительные фичи на проект могут приходить не сразу же после релиза первой версии, поэтому важно, чтобы среди разработчиков были приняты конвенции по тому, как писать код. MVP в данном случае выступает набором правил, которые можно четко описать на следующей диаграмме:
Небольшие уточнения к диаграмме:
Здесь выявляется единственный недостаток описываемой вариации MVP, а именно то, что сервис разрастается в пределах одного класса-файла. Если в iOS мы можем использовать категории и расширения для разделения одного большого класса-сервиса, то в Java придется разбивать сервис на классы подсервисы для работы с конкретным экраном.
Если у вас не более 20-25 API методов, то код остается читаемым в рамках одного файла, при условии правильного форматирования и комментирования. Для первой версии продукта можно описывать всю логику получения и отправки данных в пределах одного класса. Но не стоит увлекаться, так как технический долг со временем будет увеличиваться.
Поскольку представление можно отделить от модели, то вам не составит труда показать заказчику свою работу, пусть и со статичными данными, в случае, если бэкенд еще не готов или работает нестабильно. Благо, есть множество способов замОкать серверную часть. Если бэкендом занимается независимая команда, то при наличии согласованной документации можно быть более менее уверенным в том, что при появлении настоящего сервиса для вас не окажется неожиданностью, какие поля шлются в API, и вы успеете отладить часть, связанную с форматированием данных для представления.
Без документации, я бы все таки остановил разработку на этапе создания дизайна экранов и выводом тестовых данных на основе моделей представления. В этом случае вы также получите более менее функциональный интерфейс пригодный для демо, но самое главное вы не потратите много времени на изменение сущностей для подгонки их под реальное API, так как Presenter выступит в роли адаптера, в котором будет осуществляться преобразование данных от сервиса в форматированные данные модели представления.
Универсальность
Другим плюсом использования паттерна MVP является его универсальность для iOS и Android. Если у вас большое количество проектов разрабатывается под обе платформы, то логика Presenter-а будет универсальной, и если разработка приложений происходит по очереди, то адаптация на другой платформе будет проходить быстрее и предсказуемее, так как код логики в Presenter-е практически одинаковый. Более того, на этапе начала работы по адаптации для другой платформы уже будут готовые тест-кейсы. Конечно, если вы удосужились потратить время и написать их.
Следующим преимуществом использования паттерна MVP является разделение ресурсов разработчиков под разные платформы. Например, iOS синьор может написать часть кода под Android на уровне джуниора или мидла и быть при этом полезным, чтобы не пришлось за ним все переделывать. Сам я не сторонник “универсальных солдат”, так как лучше знать одну технологию хорошо, чем несколько на уровне джуниора или мидла. Практикующих старших разработчиков (не тимлидов или технических директоров) под обе платформы я не встречал, но не буду спорить, что такие профессионалы существуют. Основная причина дефицита таких людей, на мой взгляд, заключается в том, что в наше время очень тяжело уследить за развитием обеих технологий. Даже при условии того, что iOS и Android заимствуют некоторые конструкции друг у друга, под капотом реализация все равно разная. Но для небольших команд и “обычных” проектов подход с использованием непрофильного разработчика является вполне адекватным. Многие iOS программисты хотели бы попробовать себя в Android и наоборот. Можно отдать такому “легионеру” написать часть кода, связанного с Model или Presenter. Также будет неплохо, если человек сможет написать простой UI. Но задачи со сложными UI элементами с анимацией, оптимизацией, профилированием и многопоточностью, за исключением вызовов API сервиса, конечно, должен решать уже более опытный профильный разработчик.
Интересным случаем является тот, когда тестировщики находят баг на двух платформах сразу. Получается, что если баг в логике, то задачу можно не делить на несколько разработчиков, а отдать чинить одному человеку. При этом ему не придется сильно долго разбираться в логике разных платформ, так как используются одинаковые архитектурные решения.
Сравнение с другими известными архитектурными шаблонами
Есть еще один очень важный вопрос: почему за основу был выбран именно MVP, а не паттерны, основанные на чистой архитектуре или MVVM? В начале статьи мы постарались сконцентрироваться на том, что имеем дело с “обычными” проектами, которые желательно написать быстро, но с должным качеством и возможностью долгосрочной поддержки.
Чистая архитектура подойдет большим командам, которые имеют в штате опытных программистов под конкретную платформу, просто по причине того, что кода приходится писать в разы больше, чем в MVP, и этот код нужно правильно уметь разместить в определенном модуле.
В MVVM мы должны перестроить логику мышления на декларативную. Также MVVM очень часто ассоциируется с библиотеками Rx*, с которыми должны быть знакомы программисты, а это сразу же ставит ограничение при поиске нового разработчика, либо вы осознанно тратите время на его обучение. В этом плане MVP прозрачнее и имеет меньше ограничений на умения разработчика.
Выводы
Перейдем к выводам. Преимущества, которые нам удалось выявить для паттерна MVP, если посмотреть на него со стороны выгоды внедрения в разработку:
Подводя итоги, можно утверждать следующее: если у вас есть небольшая команда из специалистов готовых на реализацию проектов под основные мобильные платформы, вам нужны универсальные архитектурные правила, а также возможность распараллеливания задач в рамках экрана и необходимость показа демо заказчику с частично работающей функциональностью, например, если практикуются спринты, то MVP определенно ваш выбор. Дополнительным бонусом выступает удобная поддержка проекта, которая не должна привести к фразе “а вот если бы мы сейчас написали все заново” и ситуациям, когда только для того, чтобы разобраться в коде одного огромного UIViewController-а или Activity будет потрачено больше времени, чем на саму фичу.