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 опосредованно - через интерфейс.
То есть, мы, к примеру, имеем:
- Интерфейс ICustomerView, в котором определяются свойства и методы, отвечающие за поведение View;
- Класс CustomerView, который является реализацией ICustomerView и занимается непосредственно представлением данных. Кроме этого экземпляр CustomerView содержит ссылку на соответствующий Presenter, с которым и общается в рамках своей работы;
- Класс 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 сделать что-то другое.
Такой подход позволяет нам получить следующие плюсы:
- Безболезненно менять View по нашему усмотрению (или даже заменить его полностью, например, на консоль или веб-страницу, лишь бы интерфейс был реализован);
- Изменять логику обработки пользовательского ввода так, как будет угодно.
- Получить механизм, работающий в рамках "правильной" архитектуры, то есть, когда части приложения просто нотифицируют друг друга о необходимости какой-либо реакции.
View говорит презентеру: "Пользователь велел сделать то-то". Презентер делает что-то и через "полчаса" говорит View: "ну, я сделал, сообщи там как-нибудь".
"Пользователь хочет новых данных" - "Нарисуй новые данные".
"Пользователь сказал, что эти данные не годятся" - "Нарисуй вот эти данные"
"Пользователь велен их стереть" - "Нафиг, скажи, что у него нет прав".
И так далее. Никакая часть не ждет другую. Никакая часть не блокируется в ожидании (ну, потому, что не ждет). Никаких идиотских циклов ожидания с DoEvents().
Ко всему этому писать (и поддерживать!) код легко и приятно (а то иной раз просто хочется запустить мышкой в монитор, разбирая очередные 350 спагетти-строк в _button1_Click).
Пользуйтесь шаблонами проектирования, в них мудрость :) Возьмите "полуфабрикаты" в виде Composite Application Blocks, возьмите Software Factories - и они помогут вам придерживаться "правильного" пути.
И будет вам счастье.