bigpo.ru
добавить свой файл
1

Contents


Contents 1

Подход проектирования MVC 1

1 Паттерн Модель-Представление-Контроллер (Model-View-Controller) 2

2 Паттерн Модель представления (Presentation Model) 7

3 Паттерн Модель-Представление-Модель представления (Model-View-ViewModel) 8

4 Паттерн Презентация-Абстракция-Контрол (Presentation-Abstraction-Control) 9

5 Паттерн Модель-Представление-Презентатор (Model-View-Presenter) 10

Библиографический список 14


Подход проектирования MVC


Ключевым паттерном в подходе проектирования MVC является непосредственно сам шаблон (паттерн) Модель-Представление-Контроллер (Model-View-Controller), который имеет множество вариаций, такие как Модель-Представление-Презентер (Model-View-Presenter), Презентация-Абстракция-Контрол (Presentation-Abstraction-Control), Модель представления (Presentation Model), Модель-Представление-Модель представления (Model-View-ViewModel) и многие другие (некоторые из которых в свою очередь также имеют ряд модификаций), которые, по сути, представляют собой интерпретации «исходного» шаблона (паттерна) MVC, но в тоже время имеют и существенные отличия от него.

Общий вид основных шаблонов из обозначенных выше представлен на рисунке 11:

Рис.1 Основные паттерны проектирования.

1 Паттерн Модель-Представление-Контроллер (Model-View-Controller)2


По историческим причинам аббревиатурой MVC принято называть не один единственный паттерн, а целое семейство паттернов, призванное отделять представление от модели. Произошло это в силу разных обстоятельств. Отчасти из-за того что MVC не просто паттерн, а довольно объемное архитектурное решение, в котором каждый новый разработчик видел что-то свое и ставя во главу угла особенности своего проекта, реализовывал его по своему. Отчасти же из-за возраста данного паттерна, во времена его изобретения и сами приложения, и графические интерфейсы были существенно беднее, чем в наше время, с тех пор они сильно эволюционировали, и вместе с ними изменялся и сам паттерн.

Несмотря на то, что, как уже говорилось, под аббревиатурой MVC обычно понимают целое семейство паттернов, можно выделить первоначальный вариант данного архитектурного решения. На данный момент, применение MVC в оригинальном виде вряд ли будет оправдано, по разным причинам, однако рассмотрим эволюцию паттерна с самого начала.

Впервые паттерн MVC появился в уже упомянутом языке SmallTalk в конце семидесятых. В качестве задачи выступало нахождение архитектурного решения, которое позволяло бы манипулировать графическими представлениями данных некоего приложения, таким образом, чтобы изменение Представления этих данных не влияло на бизнес-логику и данные приложения, а также, чтобы была возможность иметь несколько Представлений для одной Модели. Таким решением и стал паттерн MVC [3].

В классическом варианте, MVC состоит из трех частей, которые и дали ему название.

Модель (Model)


Под Моделью, обычно понимается часть, содержащая в себе функциональную логику приложения, иными словами то, что обычно называется «Business Layer», «Бизнес-слой» или «Слой бизнес логики». Как именно организован этот слой, по большому счету не важно, однако есть ряд ключевых моментов, которые будут рассмотрены ниже. Основная цель паттерна - сделать так, чтобы Модель была полностью независима от остальных частей и практически ничего не знала об их существовании. Это позволило бы менять и Контроллер и Представление модели, не трогая саму Модель и даже позволить функционирование нескольких экземпляров Представлений и Контроллеров с одной Моделью одновременно. Поэтому Модель ни при каких условиях не может содержать ссылок на объекты Представления или Контроллера.

Обычно различают несколько типов паттернов в зависимости от роли модели.

Passive Model (пассивная модель) - Модель не имеет вообще никаких способов воздействовать на Представление или Контроллер и только используется ими в качестве источника данных для отображения. Все изменения модели отслеживаются Контроллером, и он же отвечает за перерисовку Представления, если это необходимо.

Active Model (активная модель) - Модель имеет возможность оповестить Представление о том, что в ней произошли некие изменения, и Представление может эти изменения отобразить. Как правило, механизм оповещения реализуется на основе паттерна Observer (Наблюдатель)3, Модель просто генерирует сообщение, а Представления, которые заинтересованы в оповещении, подписываются на эти сообщения, что позволяет сохранить независимость Модели, как от Контроллера, так и от Представления, не нарушая тем самым основного свойства паттерна. Классической реализацией паттерна MVC принято считать версию именно с активной Моделью.

Представление (View)


В обязанности Представления входит отображение данных, полученных от Модели. Обычно Представление имеет свободный доступ к Модели и может брать из нее данные, однако это доступ только на чтение, ничего менять в Модели или даже просто вызывать методы, приводящие к изменению ее внутреннего состояния, Представлению позволять нельзя. В случае активной Модели, Представление может подписаться на события изменения Модели и обновляться, забрав измененные данные, при получении соответствующего оповещения. Для взаимодействия с Контроллером, Представление, как правило, реализует некий интерфейс, известный Контроллеру, что позволяет менять Представления независимо и иметь несколько Представлений на Контроллер. Вообще, подмена или изменение Представления самая часто встречающаяся задача, по сути это и есть та причина, по которой создают различные паттерны разделения Модели и Представления.

Контроллер (Controller)


В задачи Контроллера входит реакция на внешние раздражители и изменение Модели и/или Представления в соответствии с заложенной в него логикой. Один Контроллер может работать с несколькими Представлениями, в зависимости от ситуации, взаимодействуя с ними через некий заранее известный интерфейс, который эти Представления реализуют. Важный моментом здесь является то обстоятельство, что в классической версии MVC Контроллер не занимается передачей данных из Модели в Представление и не является медиатором (Mediator)4 между Моделью и Представлениями. Стоит отметить, что MVC не определяет, каким именно способом Модель взаимодействует с данными и как реализован уровень доступа к данным – это лежит вне зоны ответственности данного паттерна.

Итак, типичная схема взаимодействия компонентов паттерна выглядит примерно так, как показано на рисунке 1.1 [3]: Контроллер перехватывает событие извне и в соответствии с заложенной в него логикой, реагирует на это событие, изменяя Модель, посредством вызова соответствующего метода Модели. После изменения Модель генерирует событие о том, что она изменилась, и все подписанные на это события Представления, получив его, обращаются к Модели за обновленными данными, после чего их и отображают.



Рис.1.1 Схема взаимодействия компонентов паттерна MVC.

При этом в описании оригинального паттерна также упоминалось, что выделение отдельного Контроллера не так важно как отделение Представления от Модели, и Контроллер вполне может быть интегрирован в Представление, тем более что в классическом варианте MVC логики в Контроллере не очень много.

А) Модель Документ-представление (Document-View)

Следующим этапом развития MVC стал паттерн Document-View. В этой версии MVC Контроллер интегрирован в Представление, что ни в коей мере не является нарушением основной идеи паттерна. Сделано это было по многим причинам, прежде всего, отделение Контроллера от Представления действительно не самая ключевая часть паттерна. Другой причиной являлось появление графических оболочек, встроенных в ОС (операционную систему), что позволяло не рисовать графические элементы (контролы) пользовательского интерфейса под каждый проект, а использовать готовые, предоставляемые платформой посредством соответствующего API5. Но дело в том, что в этих оболочках функции Контроллера уже были интегрированы в контролы (которые и являются Представлениями или же его частями). Свою роль сыграло и появление визуальных графических оболочек, встроенных в среду программирования, поскольку код, сгенерированный этими оболочками, использовал готовые графические элементы платформы и, как следствие, провоцировал разработку в стиле Document-View. WinForms6, вместе с Visual Studio также являются примером такой среды [3].

Недостатки MVC и Document-View


В целом MVC и Document-View как его логическое продолжение вполне адекватные паттерны для ряда задач, и с успехом применяются в большем количестве проектов, но в тоже время обладают и рядом недостатков.

Прежде всего, «чистый» MVC плохо подходит для сред типа WinForms, поскольку код, который порождают среды и библиотеки провоцирует интегрировать Контроллер в Представление. Получающийся в результате вариант паттерна в виде Document-View, в принципе, вполне приемлемое решение, однако далеко не всегда. Дело в том, что логика Контроллера, на самом деле, практически не зависит от типа Представления и сама по себе хорошо поддается тестированию. А встраивание этой логики в Представление, в случае разных типов Представлений, приводит к дублированию кода и практически исключает возможность внятного тестирования этой логики, так как тестирование пользовательского интерфейса – это отдельная задача. Да и в целом, реализация логики Контроллера вместе с представлением порождает достаточно сложную конструкцию из смеси автоматически сгенерированного и ручного кода Представления и кода логики Контроллера, который довольно сложно поддерживать.

В оригинальном паттерне именно Контроллер должен реагировать на внешние события, однако в коде, порожденном дизайнером Visual Studio, все обработчики уже встроены в Представление. Приходится либо уходить от автоматически сгенерированного кода и готового дизайнера форм, что вряд ли можно назвать адекватным решением, либо как-то пытаться адаптировать сгенерированный дизайнером код под вынесение обработчика событий во внешний Контроллер.

Важно и то, что, выделяя Контроллер в его классическом варианте, можно столкнуться с недостатком, связанным с особенностью роли Модели в классическом MVC. Как уже говорилось, MVC – это не просто паттерн, а набор паттернов, и Модель, в классическом MVC, на самом деле является медиатором (Mediator) между Контроллером/Представлением и реальной моделью домена (Domain Model) приложения (как это отражено на Рис.1.1).

В обязанности Медиатора входит транслировать вызовы Контроллера в нужные модели приложения и реализовать механизм оповещения Представлений, как правило, посредством паттерна Наблюдатель (Observer), о том, что нижележащая модель приложения изменилась. Таким образом, Модель должна обладать набором методов реализующих логику работы с пользовательским интерфейсом, однако сама Модель не может влиять напрямую на этот самый интерфейс (Представление), в противном случае это убило бы саму идею паттерна, что приводит к необходимости реализации в Представлении логики обработки событий и, как следствие, «утолщению» Представления. Плюс к этому, в некоторых случаях хотелось бы дать пользователю возможность непосредственно влиять на Представление, без привлечения событийного механизма.

Таким образом, возникает необходимость модификации паттерна, которая, с учетом упомянутых недостатков, позволяла бы следующее:

  • эффективно отделять Модель от ее Представлений;

  • пользоваться дизайнером форм и имеющимися библиотеками, без ограничений;

  • тестировать логику Контроллера независимо от Представления и сводить логику Представления к минимуму;

  • избегать лишних обращений к Модели.

Одним из возможных решений, отвечающим всем вышеприведенным требованиям, является паттерн Модель-Представление–Презентер (Model-View-Presenter).

Б) Model2: вариант MVC для Интернета

Паттерн MVC предназначался для настольных приложений, но благодаря относительно свободному формулированию он был просто приспособлен для Интернета. Model2 – популярный вариант шаблона MVC для Интернета. Здесь Model2 – это историческое имя шаблона, использующегося в платформе ASP.NET MVC Framework7 сегодня.

Шаблон Model2 предоставляет пользователям Представление через неоднократно упомянутый Наблюдатель (Observer). Действия пользователя записываются Наблюдателем и преобразуются в новый запрос HTTP. Таким образом, запросы переходят от браузера к специальному компоненту (он называется интерфейсным контроллером), который находится на веб-сервере. Интерфейсный контроллер координирует все запросы к веб-приложению.

В ASP.NET интерфейсный контроллер принимает форму HTTP-модуля переадресации URL-адресов. HTTP-модуль получает запрос и передает его соответствующему контроллеру, чтобы было выполнено запрошенное действие. По завершении операции контроллер обновляет представление и передает новые данные в интерфейс пользователя. HTTP-модуль интерфейсного контроллера получает выходные данные представления и отправляет их обратно в браузер.

На рис. 1.2 показана диаграмма последовательностей типичного взаимодействия Model2. Следует обратить внимание на то, что в нее включены только общие этапы. Например, в реализации шаблона ASP.NET MVC компонент маршрутизации URL выполняет дополнительную работу – помимо использования Контроллера [5].



Рис.1.2. Взаимодействие Model2, как в платформе ASP.NET MVC [5].

Между «изначальной» MVC и Model2 существует несколько различий. Во-первых, не существует контракта между Представлением и Моделью, как в изначальной версии MVC, основанной на упомянутых выше взаимоотношениях наблюдения. Представление не получает действий пользователя и не обрабатывает их. Контроллер передает Представление и явно передает ему отображаемые данные.

2 Паттерн Модель представления (Presentation Model)


Модель представления (Presentation Model)8 – это логическое представление UI без привязки к элементам графического интерфейса. Схема паттерна представлена на рис. 2:



Рис.2. Схема взаимодействия компонентов паттерна PM [6].

Модель представления имеет здесь следующие функции:

  • Содержит логику UI: например, при нажатии на кнопку, Модель представления уведомляется об этом и производит соответствующие действия.

  • Отображает данные Модели: производит необходимую конвертацию и форматирование данных Модели для правильного отображения пользователю.

  • Хранит состояние UI: хранение текущего выбранного элемента списка, ошибок валидации и прочее.

Представление имеет доступ к данным, хранящимся в Модели представления для дальнейших манипуляций (отображения). Преимуществом такого подхода является то, что можно создавать тесты для UI без привязки к элементам графического интерфейса [7].

3 Паттерн Модель-Представление-Модель представления (Model-View-ViewModel)


Паттерн проектирования Модель-представление-модель представления (Model-View-ViewModel)9 также известен под именем ViewModel. Схема паттерна представлена на Рис.3.



Рис.3. Схема взаимодействия компонентов паттерна MVVM [6].

Модель представления в этом шаблоне не общается напрямую с Представлением. Вместо этого Модель представления предоставляет свойства, которые могут легко быть привязаны (“прибиндены”). Представление, в свою очередь, использует эти свойства и команды для отображения данных и извлечения их из Модели представления. Также необязательно, чтобы Представление знало о Модели представления. XAML (язык разметки) позволяет декларативно, путем отражения привязать свойства Модели представления к соответствующим элементам Представления. Благодаря тому, что Модель представления предоставляет доступ через свойства к данным, уже готовым к употреблению (отформатированным и т.д.), то Представление – очень простое и не содержит никакой логики.

4 Паттерн Презентация-Абстракция-Контрол (Presentation-Abstraction-Control)


Шаблон Презентация-Абстракция-Контрол (Presentation-Abstraction-Control)10 – это шаблон проектирования программного обеспечения, являющийся производным от MVC. PAC представляет собой иерархическую структуру из агентов, каждый из которых содержат в себе триаду Презентация-абстракция-контрол [8]. Агенты (или триады) взаимодействуют друг с другом только через контрол каждой триады. Таким образом, отличием от классического паттерна MVC является то, в PAC полностью изолируется Презентация (View в MVC) и Абстракция (Model в MVC).

Структура шаблона PAC представлена на рис.4:



Рис. 4. Структура шаблона PAC.

5 Паттерн Модель-Представление-Презентатор (Model-View-Presenter)


Паттерн Модель-Представление-Презентер (Model-View-Presenter)11 является очередной модификацией MVC. Суть паттерна, опять же, состоит в отделении пользовательского интерфейса (Вид, Представление) от бизнес-логики (Модель). Этот паттерн распределяет обязательства между четырьмя компонентами, каждый из них отвечает только за свою часть обязательств, тем самым подкрепляя фундаментальный принцип «Одного Обязательства» («The Single Responsibility Principle», SRP), о котором уже упоминалось выше.

Итак, в MVP:

  • Вид ответственен только за отображение UI элементов.

  • Модель отвечает за бизнес-логику (функционал, алгоритмы и т.п.) и управление состояниями.

  • Презентер отвечает за взаимодействие между Видом и Моделью.

  • Интерфейс Вида используется, чтобы обеспечить слабое связывание Презентера с его Видом.

Обобщенная схема взаимодействий паттерна представлена на рис. 5.



Рис. 5. Схема взаимодействия компонентов паттерна MVP [3].

Пользователь взаимодействует с пользовательским интерфейсом (UI). Он совершает действие, в результате чего Вид возбуждает события. Презентер обрабатывает эти события и изменяет состояние Модели. По сути Презентер заменяет Контроллер в классическом MVC. Он может иметь прямую ссылку на экземпляр Модели. В то же время Презентер должен иметь ссылку и на экземпляр или даже экземпляры Представления, которые будут отображать данные Модели. Однако здесь, в отличие от случая с Моделью, создание конкретного экземпляра конкретного класса Представления, может обернуться большими неудобствами. При наличии тесной связи между Презентером и Представлением будет сложно реализовать замену Представлений и использование нескольких Представлений для одного Презентера, к тому же, это затруднит независимое тестирование Презентера. Да и в целом, наличие такой связи может привести к неправильной работе Презентера при изменении Представления.

Решить проблему зависимости Презентера от Представления можно с помощью паттерна «Inversion of Control12» (он же «Dependency Injection» по Фаулеру) [3]. Для этого создается интерфейс Представления, через который и будет осуществляться все взаимодействие с Представлениями.

Рассмотрим подробнее основные преимущества использования такого интерфейса (IView). Строго говоря, сам паттерн MVP вовсе не обязывает использовать для Представления именно интерфейс, но его использование здесь дает ряд преимуществ. Прежде всего, согласно упоминавшемуся принципу IoC, подобный подход уменьшает зависимость между классами. Замена интерфейса на некоторую базовую реализацию привела бы к тому, что изменение базовой реализации могло бы нарушить правильную работу использующего эту реализацию класса, в данном случае Презентера.

Помимо этого, использование интерфейса дает возможность делать Представление из объектов, находящихся на любом уровне уже существующих иерархий, предоставляемых готовыми библиотеками. Таким образом, с применением интерфейса Представление может быть реализовано как в Win, так и в Web-интерфейсе, реализовав, например, интерфейс IView от наследника System.Web.UI.Page. Очевидно, применение базовой реализации вместо интерфейса, как минимум, серьезно затруднило бы такой вариант использования Представлений.

Ну и, наконец, одной из особенностей данного паттерна является, так называемая, «скромность» Представления (по Фаулеру), то есть, в Представлении содержится вообще самый минимум логики, в некоторых источниках это так же называют «ультратонкое» Представление [3]. Поэтому реализация интерфейса IView, как правило, крайне примитивна, чего собственно и добивались, декларируя необходимость вынесения максимального количества логики из Представления в Презентер.

Задача инициализации в Презентере конкретного Представления может быть решена с помощью конструктора, в более сложных ситуациях Презентер может получать ссылку на конкретный экземпляр через специальную фабрику представлений13 или даже сам являться фабрикой, порождающей необходимые Представления в зависимости от ситуации.

Если Модель была изменена, тогда Вид тоже должен быть изменен, чтобы отразить эти изменения. В модели не должно быть никаких связей с пользовательским интерфейсом. Это позволяет повторно использовать её в различных ситуациях при совершенно разных требованиях к пользовательскому интерфейсу. Следовательно, модель не знает о существовании каких-либо визуализаций. Фактически это означает, что программист не должен создавать переменные экземпляра в модели, содержащие ссылки на визуализации, презентеры или другие объекты пользовательского интерфейса.

Существуют две разновидности паттерна MVP: Пассивный Вид (Passive View) и Наблюдающий контроллер (Контроллер-наблюдатель) (Supervising Controller), которые предусматривают различные подходы к механизму обновления Вида.

Различия паттернов Пассивный вид (Passive View) и Наблюдающий контроллер (Supervising Controller)

В случае Пассивного Вида Презентер обновляет Вид, чтобы отразить изменения Модели. Все взаимодействия с Моделью происходят только через Презентер; Вид сам по себе ничего не знает о существовании Модели.

В варианте с Наблюдающим контроллером Вид напрямую взаимодействует с Моделью, выполняя простое связывание данных без вмешательства Презентера. Презентер содержит в себе всю логику работы. Это немного упрощает работу, однако увеличивает связность.

На рис. 5.2 изображено логическое представление Пассивного Вида и Контроллера-Наблюдателя.



Рис.5.2. Логическое представление Passive View и Supervising Controller [3].

Основные преимущества использования MVP очевидны:

• Слабая связанность – Презентер является посредником между Видом и Моделью.

• Четкое разделение обязательств (в соответствии с принципом «Одного Обязательства»).

• Повторное использование кода – разделение Вида и Модели увеличивают шансы кода быть использованным повторно.

• Гибкость и расширяемость – код более приспособлен к изменениям.

Основные недостатки:

• Увеличение сложности кода. Паттерн MVP не подходит для небольших приложений.

• Необходимость применения событийного программирования.

Так же в качестве недостатка стоит отметить, что без надлежащего проектирования код Презентера часто становится очень запутанным.


Библиографический список


  1. Jeremy Miller. The Open Closed Principle: журнал MSDN Magazine.

  2. Э. Гамма, Р. Хелм, Р. Джонсон, Дж. Влиссидес. Design Patterns: Elements of Reusable Object-Oriented Software (Приемы объектно-ориентированного проектирования. Паттерны проектирования). - 16,18,38 с.

  3. Иван Бодягин. Model-View-Controller в .Net. Model-View-Presenter и сопутствующие паттерны. - журнал RSDN Magazine #2-2006.

  4. Jeff Prosise. Windows Forms: Современная модель программирования для создания GUI приложений. - Microsoft Corp.

  5. Дино Эспозито. Шаблоны представления ASP.NET.

  6. The difference between Model-View-ViewModel and other separated presentation patterns.

  7. Composite Application Guidance for WPF - June 2008. Presentation Model: msdn.

  8. Frank Buschmann, Regine Meunier, Hans Rohnert, Peter Sommerlad, and Michael Stal. Pattern: Presentation-Abstraction-Control.

  9. Тихон Баранов. Enterprise Library - набор из функциональных блоков для специалистов по разработке приложений.

  10. Microsoft Enterprise Library 4.1 Documentation.

  11. Paul Anthony. 13 бесплатных зарубежных CMS для профессионалов.

  12. Мартин Фаулер. Архитектура корпоративных программных приложений. – М.: издательский дом Вильяме, 2006.

  13. Дино Эспозито. Microsoft ASP.NET 2.0 Базовый курс. – Русская редакция, 2007. – 614 с.

  14. Сергей Мартыненко. Модульное тестирование. 2006.



1 [«The difference between Model-View-ViewModel and other separated presentation patterns»].

2 В дальнейшем просто MVC.

3 Паттерн Observer (Наблюдатель), известен так же под именем Publish/Subscribe (Публикатор/Подписчик), предназначен для организации наблюдения за состоянием объекта. Суть паттерна в том, что несколько объектов, называемых «наблюдателями» или «слушателями» (listeners), регистрируются, сами или с чьей-либо помощью, в качестве обработчиков события (event), которое инициируется наблюдаемым объектом – «субъектом» (Subject) при изменении своего состояния. Таким образом, достигается независимость «Субъекта» от «Наблюдателей».


4 Паттерн Mediator (Медиатор), заключается в реализации единого объекта, который скрывает взаимодействие группы объектов между собой. Предназначен для уменьшения связности и сложности системы за счет того, что с группой объектов можно работать как с единым целым.


5 Интерфейс прикладного программирования (Application Programming Interface) — набор готовых классов, функций, структур и констант, предоставляемых приложением (библиотекой, сервисом) для использования во внешних программных продуктах.

6 Windows Forms — название той части .NET Framework, которая отвечает за графический интерфейс пользователя в приложениях операционной системы Windows. Windows Forms «оборачивает» в управляемый код стандартные элементы интерфейса Windows, доступные при помощи Win32 API. Причем управляемый код — классы, реализующие API для Windows Forms, не зависят от языка разработки [4].

7 ASP.NET MVC Framework — фреймворк для создания веб-приложений, который реализует паттерн Model-view-controller. Данный фреймворк добавлен Microsoft в ASP.NET.

8 В дальнейшем просто PM.

9 В дальнейшем просто MVVM.

10 В дальнейшем просто PAC.

11 В дальнейшем просто MVP.

12 Inversion of Control (IoC – в дальнейшем), это даже не паттерн, а архитектурный принцип, который используется для уменьшения связности между объектами. Суть его довольно проста. Допустим объект x (класс X) вызывает некий метод объекта y (класс Y), в этом случае считается, что X зависит от Y. Данная зависимость может быть «перевернута» путем введения третьего класса I, называемого интерфейсным классом, который содержит в себе все методы, которые x вызывает у объекта y, при этом Y должен реализовывать интерфейс I. После подобного преобразования X и Y зависят от I, но не зависят друг от друга, более того, если ранее X так же транзитивно зависел от всех классов от которых зависит Y, то теперь и эта зависимость оказалась разорвана.

13 Паттерн Factory Method (фабричный метод), как и другие «строительные» паттерны, предназначен для создания объекта без указания конкретного класса реализующего этот объект. В данном случае это делается путем объявления в самом классе только публичного интерфейса для создания объекта, само же создание делегируется классам-наследникам.