Страницы

Поиск по вопросам

понедельник, 1 октября 2018 г.

Как начать пользоваться MVP + WinForms?

Пишу приложение с использованием БД - Firebird. Компьютеры у людей не очень мощные и WPF там тормозит. Поэтому необходимо на WinForms (прощай удобный MVVM). Узнал что для удобной работы люди используют MVP.
Есть какой-то вводный материал или статьи нормальные на эту тему, или может собственный опыт у кого есть? Ибо самостоятельное выяснение нормальных результатов не дало.


Ответ

Немного ссылок:
Вводная от Википедии
Model-View-Presenter и сопутствующие паттерны
Особенности реализации MVP для Windows Forms (тут, на мой взгляд, немного наворочено, но для ознакомления тоже подойдет)

Изложу также свой опыт.
Для каждого экрана должно быть три модуля: модель, представление и презентер.
Модель отвечает за работу с данными (загрузка/сохранение). Она является своеобразным фасадом к некоторому источнику данных или к слою доступа к данным. Ее задача -- загружать и сохранять данные согласно задачам конкретного экрана Представление отвечает за пользовательский интерфейс. Это, по сути, и есть ваш экран. Представление вызывает методы презентера (например, в обработчиках событий), а также предоставляет методы для отображения данных (они вызываются презентером). Презентер отвечает за взаимодействие между представлением (которое умеет только показывать данные и реагировать на действия пользователя) и моделью (которая знает только про данные). Как правило, это включает в себя логику представления данных, валидацию и другие вещи, тесно связанные с интерфейсом.
Направление ссылок получается следующим: представление <-> презентер -> модель. Важно запомнить, что эта тройка нужна для каждого экрана. Это не что-то единое для всего приложения. Иногда возможны исключения: в случае необходимости единого управления несколькими экранами может быть несколько представлений/моделей и всего один презентер.
Например, на экране есть кнопка "Загрузить заказы". В обработчике кнопки вызывается соответствующий метод презентера -- LoadOrders(). Внутри метода презентера идет обращение к модели, внутри модели непосредственно загружаются данные. После того, как презентер получил от модели данные, он может как-то преобразовать их для показа или выполнить над ними какую-то логику. После этого данные либо возвращаются из метода, либо -- что более канонично -- вызывается метод представления SetOrders(), внутри которого данные уже непосредственно загружаются в какой-либо контрол.
По коду получается следующая структура. Представление:
interface IOrdersView { void SetOrders(Order[] orders); }
class OrdersForm : Form, IOrdersView { private OrdersPresenter presenter;
public void OrdersForm() { ... // презентер можно создавать в конструкторе, // а можно иметь отдельный метод инициализации экрана, // который будет вызываться сразу после создания формы, // создавать презентер и вызывать у него метод загрузки данных presenter = new OrdersPresenter(this, new OrdersModel()); }
void btnLoadOrders_Click(...) { presenter.LoadOrders(); }
void IOrdersView.SetOrders(Order[] orders) { // загружаем данные в контрол } }
Презентер:
class OrdersPresenter { private readonly IOrdersView view; private readonly IOrdersModel model;
OrdersPresenter(IOrdersView view, IOrdersModel model) { this.view = view; this.model = model; }
void LoadOrders() { var orders = model.LoadOrders(); view.SetOrders(orders); } }
Модель:
interface IOrdersModel { Order[] LoadOrders(); }
class OrdersModel : IOrdersModel { Order[] IOrdersModel.LoadOrders() { // тут логика по загрузке } }
Интерфейсы в принципе опциональны, но удобны для тестирования и моков.

Вопросы из комментариев:
А как мне допустим если в таблице (DataGridView) изменили запись, записать эти изменения в базу. Сразу причем. То есть как только закончили редактирование сразу в базу.
Отслеживаете событие изменения ячейки/строки, вызываете презентер, передав ему измененную запись, дальше презентер при необходимости валидирует и передает запись на сохранение модели. А модель уже обращается к слою доступа к данным (DAL'у).
В интерфейсе IOrdersView объявлен метод SetOrders(), который принимает значение типа Order[]. Но что это за тип? Где он описан?
В данном случае это какой-то пользовательский тип. Важное тут -- что SetOrders() принимает некоторые данные, которые готовы для отображения и которые представление (в данном случае форма) знает, как отображать. Это может быть и DataTable, и массив строк. Что угодно.
Во View Вы создаете экземпляр класса OrdersPresenter и в качестве параметра передаете экземпляр класса OrdersModel. Насколько это укладывается в концепцию? Разве View и Model не должны быть развязаны и ничего не знать друг о друге?
В идеале -- да, представление и модель должны быть развязаны и не должны ничего знать друг о друге. Создание экземпляра модели внутри представление является некоторым упрощением, и в целом втискивается в шаблон, поскольку представление не использует модель явным образом. Если же оставаться пуристом, то есть следующие варианты:
Создавать модель внутри презентера. Главный недостаток -- плохая тестируемость презентера, поскольку невозможно подменить модель своей реализацией (а в тестах на презентер она всегда подменяется). Обойти это можно имея в презентере два конструктора -- один создает модель по умолчанию, второй -- принимает модель извне. Хотя и тут найдутся пуристы, утверждающие, что иметь специальные члены, которые используются только в тестах, плохо. Поэтому я иду по простому пути и всегда создаю модель в представлении. Передавать в представление уже созданный презентер. Т.о. модель будет инициализирована по крайней мере вне текущего представления. Однако по большому счету это ничего не дает, т.к. текущее представление будет открываться из другого представления, и теперь уже другому представлению нужно будет что-то знать о модели.
Правильно я понял, что если у меня в программе 100 таблиц и мне нужно 100 форм для работы с ними, то для каждой формы я должен содать свой интерфейс IOrdersModel и класс унаследованный от него, который может быть уже не Orders, а Person, например и свой Presenter? Интерфейс типа IOrdersView тоже для каждой формы создавать?
В общем случае да, для каждой XXXForm у вам должны быть XXXView, XXXPresenter и XXXModel. Однако если форм действительно много и они очень однотипные, то, возможно, достаточно будет обобщенных IView, Presenter, IModel, где T -- конкретный тип редактируемой сущности.

Комментариев нет:

Отправить комментарий