Сообщений 187    Оценка 1562 [+5/-3]         Оценить  
Система Orphus

Model-View-Controller в .Net

Model-View-Presenter и сопутствующие паттерны

Автор: Иван Бодягин
The RSDN Group

Источник: RSDN Magazine #2-2006
Опубликовано: 25.07.2006
Исправлено: 10.12.2016
Версия текста: 1.1
Введение
«Оригинальный» MVC
Недостатки MVC и Document-View
Model-View-Presenter
Model
Presenter
View
Почему интерфейс?
Отличия от MVC
Заключение
Что почитать

Демонстрационный проект

Введение

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

«Оригинальный» MVC

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

Впервые паттерн MVC появился в языке SmallTalk в конце семидесятых. Собственно задача была хорошо всем знакомая, надо было придумать архитектурное решение, которое позволяло бы манипулировать графическими представлениями данных некоего приложения, таким образом, чтобы изменение Представления этих данных не влияло на бизнес-логику и данные (Модель) приложения, а так же, чтобы была возможность иметь несколько Представлений для одной Модели. Таким решением и стал паттерн MVC, идея которого родилась в недрах Xerox PARK, и получила свое первое публичное упоминание в документации к SmallTalk’80. В классическом варианте, MVC состоит из трех частей, которые и дали ему название.

Model (Модель)

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

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

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

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

СОВЕТ

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

Более подробно с паттерном можно ознакомится здесь: http://en.wikipedia.org/wiki/Observer_pattern и здесь: http://dofactory.com/Patterns/PatternObserver.aspx

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

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

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

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

ПРИМЕЧАНИЕ

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

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


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

Следующим этапом развития MVC стал паттерн Document-View, хорошо известный по таким библиотекам как Turbo Vision (Pascal 6.0, ох ностальгия, Microsoft Foundation Class Library и многих других, вплоть до WinForms.

ПРИМЕЧАНИЕ

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

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

У той же Microsoft, которая выкладывает довольно много статей по архитектурным решениям в свободный доступ, есть несколько публикаций и по MVC, и там можно легко заметить, что все примеры чистого MVC даются не на основе WinForms приложений, а на примере ASP.NET, где, в отличии от WinForms, есть относительно четкое разделение между Контроллером и Представлением, про WinForms же есть только упоминание о том, что, как правило, в подобного рода приложениях Контроллер интегрирован в Представление.

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

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

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

Если же попытаться выделить Контроллер «в лоб», то можно нарваться на следующие неприятности...

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

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

СОВЕТ

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

Более подробно с паттерном можно ознакомится здесь: http://en.wikipedia.org/wiki/Mediator_pattern и здесь: http://dofactory.com/Patterns/PatternMediator.aspx

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

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

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

Model-View-Presenter

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

Model

Если бы мы разрабатывали наше приложение в привычном стиле, навязываемом WinForms, то скорее всего начали бы с набрасывания формочек, однако в данном случае мы зайдем с другой стороны и начнем с реализации Модели. И так, наша Модель должна уметь принимать данные температуры как в цельсиях так и в фаренгейтах и пересчитывать их в других величинах. Типичная Модель для данной задачи могла бы выглядеть следующим образом:

        public
        class Model
   {
      privatedouble _valueFahrenheit = 32;
      privatedouble _valueCelsius = 0;

      /// <summary>/// Градусы в шкале Фаренгейта/// </summary>publicdouble valueFahrenheit
      {
         get { return _valueFahrenheit; }
         set 
         { 
            _valueFahrenheit = value;
            _valueCelsius = (_valueFahrenheit - 32) * 5 / 9;
         }
      }

      /// <summary>/// Градусы в шкале Цельсия/// </summary>publicdouble valueCelsius
      {
         get { return _valueCelsius; }
         set 
         { 
            _valueCelsius = value;
            _valueFahrenheit = _valueCelsius * 9 / 5 + 32;
         }
      }
   }

Presenter

Следующим этапом будет реализация Presenter-а, эта сущность заменяет Контроллер в классическом MVC - она делает все тоже самое, что и Контроллер, плюс кое-что еще. Более подробно о его функциях мы поговорим позже.

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

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

СОВЕТ

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

Более подробно с этим принципом можно ознакомиться здесь: http://en.wikipedia.org/wiki/Inversion_of_control и здесь: http://www.martinfowler.com/articles/injection.html

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

        public
        interface IView
   {
      /// <summary>/// Вывод градусов Фаренгейта/// </summary>void SetFarenheit(double value);

      /// <summary>/// Вывод градусов Цельсия/// </summary>void SetCelsius(double value);

      /// <summary>/// Ввод нового значения градусов/// </summary>double InputDegree { get; }

      /// <summary>/// Событие ввода значения по Фаренгейту/// </summary>event EventHandler<EventArgs> SetFarenheit;

      /// <summary>/// Событие ввода значения по цельсию/// </summary>event EventHandler<EventArgs> SetCelsius;
   }

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

СОВЕТ

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

Более подробно об этом паттерне можно почитать здесь: http://en.wikipedia.org/wiki/Factory_method_pattern и здесь: http://dofactory.com/Patterns/PatternFactory.aspx

Наличие интерфейса так же позволяет уже на этом этапе полностью завершить разработку Presenter-а и заняться его тестированием, несмотря на то, что конкретные Представления еще не готовы и ни один контрол еще не брошен на форму... ( И так, Presenter может выглядеть примерно следующим образом:

        public
        class Presenter
   {
      private Model _model = new Model();
      private IView _view;

           /// <summary>
           /// В конструтор передается конкретный экземпляр представления
           /// и происходит подписка на все нужные события.
           /// <summary>
public Presenter(IView view)
      {
         _view = view;
         _view.SetCelsius += new EventHandler<EventArgs>(OnSetCelsius);
         _view.SetFarenheit += new EventHandler<EventArgs>(OnSetFarenheit);
         RefreshView();
      }

      /// <summary>/// Обработка события, установка нового значения градусов по Фаренгейту/// </summary>privatevoid OnSetFarenheit(object sender, EventArgs e)
      {
         _model.valueFahrenheit = _view.InputDegree;
         RefreshView();
      }

      /// <summary>/// Обработка события, установка нового значения градусов Цельсия/// </summary>privatevoid OnSetCelsius(object sender, EventArgs e)
      {
         _model.valueCelsius = _view.InputDegree;
         RefreshView();
      }

      /// <summary>/// Обновление Представления новыми значениями модели./// По сути Binding (привязка) значений модели к Представлению. /// </summary>privatevoid RefreshView()
      {
         _view.SetCelsius(_model.valueCelsius);
         _view.SetFarenheit(_model.valueFahrenheit);
      }
   }

В реальных приложениях это, как правило, гораздо более сложный класс, но об этом мы поговорим позже...

View

В данном случае реализовать интерфейс можно в классе реализующем форму, причем сделать это можно в отдельном проекте, вдруг потом захочется реализовать и web-интерфейс? ( Разработка конкретного Представления совсем тривиальна, форма представления наследуется от созданного нами ранее интерфейса, при этом реализация свойств интерфейса примитивна – по сути это просто перекладывание значений из интерфейса в контролы и наоборот.

        public partial class FormView : Form, IView
   {
      #region Реализация IView

      /// <summary>/// Вывод градусов Фаренгейта/// </summary>publicdouble Farenheit
      {
         set { _farenheitBox.Text = value.ToString("N2"); }
      }

      /// <summary>/// Вывод градусов Цельсия/// </summary>publicdouble Celsius
      {
         set { _celsiusBox.Text = value.ToString("N2"); }
      }

      /// <summary>/// Ввод нового значения градусов/// </summary>publicdouble InputDegree
      {
         get { return Convert.ToDouble(_inputBox.Text); }
      }

      #endregion/// <summary>/// Обработка событий тоже примитивна, они просто пробрасываются/// в соответствующие события Presenter-а/// </summary>privatevoid _celsiusButton_Click(object sender, EventArgs e)
      {
         if (SetCelsius != null)
            SetCelsius(this, EventArgs.Empty);
      }

      privatevoid _farenheitButton_Click(object sender, EventArgs e)
      {
         if (SetFarenheit != null)
            SetFarenheit(this, EventArgs.Empty);
      }
   }

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

        static
        class Program
   {
      [STAThread]
      staticvoid Main()
      {
         FormView view = new FormView();
         Presenter presenter = new Presenter(view);
         Application.Run(view);
      }
   }

Вот собственно и все, паттерн MVP в своем простейшем варианте перед вами, самое время его обсудить.

Почему интерфейс?

Хотелось бы более подробно остановиться на интерфейсе IView, что дает его использование, и почему бы не заменить его на базовый класс, с некоторой примитивной реализацией...

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

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

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

Отличия от MVC

Чем же данный паттерн отличается от «классического» MVC и почему Controller назвали Presenter-ом?


Прежде всего, можно заметить, что, количество связей между сущностями уменьшилось, как правило, в MVP Модель не общается с Представлением даже опосредовано, через механизм оповещений, но это не обязательное условие, более глубокие отличия лежат как раз в области Presenter-а.

Как уже упоминалось выше, в MVC Модель, на самом деле, является медиатором, между Контроллером/Представлением и реальной моделью домена (Domain Model) приложения. В MVP-же, что видно на схеме, такого медиатора нет и его функции берет на себя Presenter, таким образом Presenter, в отличие от Контроллера, общается непосредственно с моделью приложения, а не с неким промежуточным звеном. Это позволяет, в случае необходимости, общаться Presenter-у с Представлениями, минуя Модель приложения и событийный механизм с этим связанный, так как Presenter в отличии от Контроллера, обладает необходимыми знаниями о Представлениях, а так же удаляет лишнюю логику обработки событий от Модели из Представления.

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

Вследствии этого, как уже так же упоминалось, в отличии от MVC, Представление в MVP является «скромным» или «ультра-тонким» и практически не содержит логики независимой от конкретного Представления. Эту особенность, помимо всего прочего, очень любят маньяки от тестирования типа того же Фаулера, любители TDD, продвигатели XP и прочих Agile методологий, поскольку «скромные» представления сводят код не покрытый модульными тестами к минимуму.

Иными словами, если в случае MVC Контроллер играет довольно примитивную роль вызывальщика соответствующих методов Модели при реакции на событие, то Presenter в MVP уже берет на себя ведущие функции по управлению Моделями и Представлениями. Боюсь только, что данная особенность не нашла отражения в приведенном примере.

Заключение

Выше была описана, если можно так выразиться, базовая версия паттерна, однако внутри генеральной линии возможно несколько модификаций и вариантов применения.

Например, упомянутый здесь уже неоднократно Фаулер, выделяет аж три возможных модификации MVP, это Presentation Model, Supervising Controller и Passive View. Все они отличаются исключительно способом взаимодействия Presenter-а и Представления, а точнее способами передачи данных представлению. Если мы себе позволяем передавать в Представление бизнес-объекты, вместо примитивных типов, то это Presentation Model, если же позволили Представлению знать о наличии модели и самому забирать из нее данные, то это Supervising Controller, ну а самый простой случай, когда логика Представления действительно минимальна то с нами Passive View. В каждом конкретном случае выбор между этими тремя модификациями паттерна зависит от того, на сколько умную автоматическую привязку бизнес объектов к интерфейсу Представления вы можете использовать и какая степень покрытия тестами логики Presenter-а вас устроит... Ну и, само собой, от сложности бизнес-объектов и интерфейсов.

Для реальных ситуаций, которые, как правило, несколько сложнее приведенного примера, существуют модификации паттерна позволяющие выстроить определенную иерархию. Выделяют два варианта таких иерархических паттернов – это Hierarchical Model-View-Controller (HMVC) и Presentation-Abstraction-Control (PAC), который пришел из мира Java. Отличие заключается в том, что HMVC позволяет выстраивать независимые иерархии, отдельно для Представлений, отдельно для Контроллера/Presenter-а, ну и само-собой для Модели, с прямыми ссылками между собой, то есть, например, Представление может ссылаться непосредственно на дочернее Представление. В свою очередь PAC, более строгий паттерн, и предписывает иметь связи между собой только Контроллерам, другие сущности доступны извне исключительно через свой Контроллер.

В целом, применение MVP, вместо уже ставшего традиционным Document-View, с одной стороны, несколько увеличивает объем ручного кодирования, однако с другой, позволяет покрыть большую часть логики тестами, уменьшает связность между компонентами и может служить основой для реализации механизма «скинов» (оболочек, шкурок) приложения, вплоть до того, что для одного и того же «движка», можно иметь как Win, так и Web «шкурку».

Что почитать

  1. Wikipedia: Model-View-Controller (http://en.wikipedia.org/wiki/Model_view_controller)
  2. Twisting The Triad (http://www.object-arts.com/Papers/TwistingTheTriad.PDF) by Andy Bower, Blair McGlashan
  3. GUI Architectures (http://www.martinfowler.com/eaaDev/uiArchs.html) by Martin Fowler
  4. Microsoft Journal: Model-View-Presenter (http://msdn.microsoft.com/msdnmag/issues/06/08/DesignPatterns/default.aspx) by Jean-Paul Boodhoo
  5. Supervising Controller (http://www.martinfowler.com/eaaDev/SupervisingPresenter.html) by Martin Fowler
  6. Passive View (http://www.martinfowler.com/eaaDev/PassiveScreen.html) by Martin Fowler
  7. Presentation Model (http://www.martinfowler.com/eaaDev/PresentationModel.html) by Martin Fowler


Эта статья опубликована в журнале RSDN Magazine #2-2006. Информацию о журнале можно найти здесь
    Сообщений 187    Оценка 1562 [+5/-3]         Оценить