2/17/2007 10:30:00 PM

M-V-P (Model-View-Presenter) - это разновидность шаблона M-V-C (Model-View-Controller) с некоторыми существенными отличиями.
Я не буду описывать отличия, если кто-то интересуется, то суть этих отличий кратко описана у Дэррона Шэлла здесь и детально у Мартина Фаулера здесь.

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

Я остановлюсь на реализации View и Presenter.

Справедливости ради нужно отметить, что Composite UI Application Block предлагает именно M-V-C, но при работе со Smart Client Software Factory вы будете общаться именно с M-V-P. Класс презентера (как и некоторые другие) не являются частью CAB, но будут созданы фабрикой в общем модуле вашего приложения.
В Composite Web Application Block презентер уже имеется изначально, поэтому я думаю, что соответствующая реализация будет включена и в следующий релиз CAB.

M-V-P в данном случае предполагает наличие View - пользовательского интерфейса и наличие Presenter'а, который содержит логику работы пользователя над моделью.
При этом View "знает" о наличии Presenter'а "напрямую" в то время, как Presenter "знает" о наличии View опосредованно - через интерфейс.

То есть, мы, к примеру, имеем:

  1. Интерфейс ICustomerView, в котором определяются свойства и методы, отвечающие за поведение View;
  2. Класс CustomerView, который является реализацией ICustomerView и занимается непосредственно представлением данных. Кроме этого экземпляр CustomerView содержит ссылку на соответствующий Presenter, с которым и общается в рамках своей работы;
  3. Класс CustomerViewPresenter, который ничего не знает о CustomerView. Вместо этого он знает об интерфейсе ICustomerView, ссылку на который содержит.

В коде это выглядит так:

 

//Interface
public interface ICustomerView
{
    void ShowCustomerInfo(Customer customer);
}
 
//Presenter
public class CustomerViewPresenter : Presenter<ICustomerView>
{
    public override void OnViewReady()
    {
        base.OnViewReady();
        //Get the customer somewhere
        .....
        View.ShowCustomerInfo(customer);
    }
}
 
//View
public partial class CustomerView : UserControl, ICustomerView
{
    public CustomerView()
    {
        InitializeComponent();
    }
 
    [CreateNew]
    public CustomerViewPresenter Presenter
    {
        set
        {
            _presenter = value;
            _presenter.View = this;
        }
    }
 
    protected override void OnLoad(EventArgs e)
    {
        _presenter.OnViewReady();
    }
 
    public void ShowCustomerInfo(Customer customer)
    {
        if (this.InvokeRequired)
        {
            this.BeginInvoke(new MethodInvoker(delegate() { ShowCustomerInfo(customer); }));
            return;
        }
 
        .....
    }
}

 

Отсюда видно, что при создании экземпляря CustomerView ObjectBuilder создает экземплярCustomerViewPresenter, ссылка на который теперь доступна внутри CustomerView. Сам же презентер получает ссылку на экземпляр, реализующий известный ему интерфейс, поэтому ему все равно, какой тип пользовательского интерфейса скрывается за ICustomerView.

Важно отметить то, что в случае M-V-P слой View сам обрабатывает пользовательский ввод и "уведомляет" презентер о необходимости получить/обработать данные модели, а так же о своем состоянии. В указанном примере View уведомляет Presenter о том, что он загружен и готов к работе (событие OnLoad в CustomerView).

Презентер же, выполнив какие-то действия (например, получив от ICustomerService экземпляр класса Customer), сообщает View о необходимости отобразить информацию об этом кастомере.

Здесь очень важным является тот момент, что View никакой информацией о логике Presenter'а  не располагает, поэтому реализация метода ShowCustomerInfo делается потокобезопасной. На самом деле, Presenter может начать обрабатывать данные асинхронно для того, чтобы пользовательский интерфейс не блокировался на время работы. Пока ведется обработка данных, Presenter может "просить" View информировать пользователя о прогрессе и лишь после окончания обработки вызвать метод ShowCustomerInfo. Поэтому View должен быть готов к тому, что практически любой вызов от презентера придет из контекста другого потока, реализуя методы интерфейса ICustomerView потокобезопасными.

На самом деле, это очень простая схема и именно так и нужно делать всегда: View лишь уведомляет Presenter о том, что пользователь захотел сделать что-то (например, вызывая _presenter.ApproveCustomer). View не в праве расчитывать на то, что Presenter ответит немедленно, вернув какой-то результат. Строго говоря, результаты вообще не в компетенции View, View не в праве решать, что делать с результатом. Вместо этого обработкой данных и получением результата занимается Presenter и после того, как операция выполнена, просто заставляет View сделать что-то другое.

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

  1. Безболезненно менять View по нашему усмотрению (или даже заменить его полностью, например, на консоль или веб-страницу, лишь бы интерфейс был реализован);
  2. Изменять логику обработки пользовательского ввода так, как будет угодно.
  3. Получить механизм, работающий в рамках "правильной" архитектуры, то есть, когда части приложения просто нотифицируют друг друга о необходимости какой-либо реакции.
    View говорит презентеру: "Пользователь велел сделать то-то". Презентер делает что-то и через "полчаса" говорит View: "ну, я сделал, сообщи там как-нибудь".
    "Пользователь хочет новых данных" - "Нарисуй новые данные".
    "Пользователь сказал, что эти данные не годятся" - "Нарисуй вот эти данные"
    "Пользователь велен их стереть" - "Нафиг, скажи, что у него нет прав".
    И так далее. Никакая часть не ждет другую. Никакая часть не блокируется в ожидании (ну, потому, что не ждет). Никаких идиотских циклов ожидания с DoEvents().

Ко всему этому писать (и поддерживать!) код легко и приятно (а то иной раз просто хочется запустить мышкой в монитор, разбирая очередные 350 спагетти-строк в _button1_Click).

Пользуйтесь шаблонами проектирования, в них мудрость :) Возьмите "полуфабрикаты" в виде Composite Application Blocks, возьмите Software Factories - и они помогут вам придерживаться "правильного" пути.
И будет вам счастье.

Tags: , ,

Comments (1) -

4/23/2007 7:11:48 PM

Дмитрий Курилович

Я вот придерживаюсь некой подобной схемы при разработке веб контролов:
в класе веб контрола (inherited from WebControl), ну например TabControl, определяю параметры контрола (с поддержкой дизайн тайма), реализовую логику контрола, но собственно отображение состояния делегирую реализации некого интерфейса ITabControlUI - специальный интерфейс отражающий механику контрола:
interface ITabControlUI {
  Control Control { get; }
  void Initialize(TabControl control);
  event EventHandler SelectedTabChanged;
}
в таком случае при использовании TabControl на странице - мы имеем дело с веб контролом - задаем все свойства в дизайнере страницы. сам контрол создает реализацию ITabControlUI - она лежит в веб проекте в виде ЮзерКонтрола, добавляет в свою коллекцию контролов, и вызывает методы интерфейса для отображения состояния, подписывается на события.
в таком случае мы имеем:
- поддержка дизайн тайма для контрола - мы используем веб контрол а не юзер контрол, для которого о передаче параметров не может быть и речи;
- внешний вид полностью кастомизируется при помощи юзер-контрола реализовав ITabControlUI;
- вся сложность логики самого контрола раз и навсегда внутри веб-контрола.

Дмитрий Курилович

Comments are closed

Powered by BlogEngine.NET 2.5.0.6

About the author

Alexey Raga Alexey Raga
.NET software developer.

E-mail me Send mail

Twitter

Widget Twitter not found.

Root element is missing.X


Recent posts

Archive

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.

© Copyright 2012

Sign in