Страницы

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

понедельник, 16 декабря 2019 г.

Как правильно реализовывать модель MVP?

#c_sharp #winforms #mvp


Пытаюсь разобраться с тем, как улучшить работу с WinForms и разделить логику и представление.
Наткнулся на шаблон MVP с краткими примерами и пытаюсь понять как им пользоваться.
На данный момент имею следующий код, но мне кажется я делаю что-то не так. К тому же
у меня в эту модель совсем не укладывается реализация многопоточности. Отсюда вопрос,
как правильно реализовать шаблон проектирования MVP в WinForms?

View реализация

interface ICustomer
{
    string FirstName { get; set; }
    string LastName { get; set; }
    string SurName { get; set; }
    string Address { get; set; }
    string Code { get; set; }

    event Action Search;
    event Action Cancel;
}

public partial class Customer : Form, ICustomer
{
    public string FirstName
    {
        get { return lblFirstName.Text; }
        set { lblFirstName.Text = value; }
    }

    public string LastName
    {
        get { return lblLastName.Text; }
        set { lblLastName.Text = value; }
    }

    public string SurName
    {
        get { return lblSurname.Text; }
        set { lblSurname.Text = value; }
    }

    public string Address
    {
        get { return lblAddress.Text; }
        set { lblAddress.Text = value; }
    }

    public string Code
    {
        get { return txtCode.Text; }
        set { txtCode.Text = value; }
    }

    public event Action Search;
    public event Action Cancel;

    public Customer()
    {
        InitializeComponent();
    }

    private void btnSearch_Click(object sender, EventArgs e)
    {
        Search();
    }

    private void btnCancel_Click(object sender, EventArgs e)
    {
        Cancel();
    }
}


Presenter реализация

class CustomerPresenter 
{
    public ICustomer View;
    public ICustomerModel Model;

    public CustomerPresenter(ICustomer view, ICustomerModel model)
    {
        View = view;
        Model = model;

        View.Search += View_Search;
        View.Cancel += View_Cancel;
    }

    private void View_Search()
    {
        new Task(() => {
            View.FirstName = "Alex Krass";
        }).Start();
    }

    private void View_Cancel()
    {

    }
}


Модель и вызов

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

class UnityIoC
{
    private static UnityContainer unityContainer;

    public static UnityContainer Instance 
    {
        get
        {
            if (unityContainer == null) CreateContainer();
            return unityContainer;
        }
    }

    private static void CreateContainer()
    {
        unityContainer = new UnityContainer();
        unityContainer.RegisterType();
        unityContainer.RegisterType();
    }
} 

static void Main()
{
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);

    ApplicationContext context = new ApplicationContext()
    {
        MainForm = UnityIoC.Instance.Resolve().View as Form
    };
    context.MainForm.Show();

    Application.Run(context);
}


Ну соответственно при нажатии на кнопку Search я не могу обновить UI, неужели придется
в Presenter пробрасывать сам TextBox в ICustomer и вызывать BeginInvoke? Или я просто
чего-то недопонимаю в реализации MVP?

UPD: 

Вопрос с обновлением UI без его блокирования вроде решился через использование таймера,
когда информацию надо забирать по мере выполнения Task и через TaskScheduler, когда
надо обновлять после выполнения Task.

**Вариант 1**

timer = new Timer() { Interval = 1000 };
timer.Tick += timer_Tick;
timer.Start();

private void timer_Tick(object sender, EventArgs e)
{
    UpdateView();
}

**Вариант 2**
task = new Task(new Action(UpdateModel));
task.ContinueWith(new Action(UpdateView), TaskScheduler.FromCurrentSynchronizationContext());
task.Start();


private void UpdateView(Task task = null)
{
    view.SomeVal = SomeVal;
    view.SomeValNext = SomeValNext;
}

    


Ответы

Ответ 1



Вопросы по интерпретации и реализации паттернов проектирования почти всегда холиварные, поэтому сразу предупреждаю, что все что написано ниже - это мое личное видение основанное на моих же логике, понимании и практике использования WinForms. Про еще один вариант интерпретации и реализации MVP можно почитать в статье на хабре По аналогии с биологией начнем с простейших одноклеточных (одно-оконных) приложений содержащих только простые контролы (кнопки, картинки, текстовые поля со вводом или без). 1. Одноклеточные Определимся с компонентами паттерна и тем, какие элементы приложения к ним относятся. M - Model - отдельный класс инкапсулирующий работу с данными. Все операции над данными производятся только в нем. Если рассматривать модель под микроскопом, то в ней можно рассмотреть много-поточную или асинхронную обработку тяжелых вычислений, обращения к сервисам и базам данных и прочие слои свойственные моделям. V - View - визуальное представление данных модели. Сюда можно отнести все контролы нашей, пока единственной, формы, которые собственно отображают нашу модель с выбранного ракурса. P - Presenter - не буду выдумывать специальный перевод, остановимся на длинном но более-менее точном определении - компонент, который отвечает за получение данных из модели и знает как и когда их нужно отображать, а также обрабатывает пользовательский ввод, дергает модель за соответствующие методы и обрабатывает события модели. В случае простейших, презентером будет выступать класс формы, в логике которого мы и организуем передачу данных из модели в контролы для представления данных и обработку событий контролов, для передачи в модель действий пользователя. 2. Многоклеточные Когда окон становится больше одного или появляются сложные контролы, предыдущая модель приложения все еще имеет право на существование, но работать с ней становится неудобно. Добавим класс-презентер, для которого в качестве представления будут выступать наши одноклеточные описанные выше. Его главная задача - передать частным перезентерам нужный кусочек модели для работы, агрегировать события от них и передавать эти события в нужном порядке и количестве в модель, а также маршрутизировать события модели к частным презентерам. Для частных презентеров - общий будет выступать в роли модели. Также в нем можно реализовать много-поточное/асинхронное обращение к модели, т.к. он связан только с моделью и подчиненными презентерами, а не элементами UI, за которые отвечают частные презентеры. Таким образом получается что наше приложение состоит из множества простых, относительно самостоятельных фрагментов, под руководством старшего презентера, обеспечивающего их согласованную работу. 3. Меняем паттерн Приложение растет и усложняется и в определенный момент даже многоклеточная модель становится неудобной. Тут самое время вспомнить, что WinForms поддерживает DataBinding. А в связи с этим можно немного поменять шаблон. В сети он часто упоминается как MVPVM. Тут появляется новый компонент - VM - view-model, и роль презентера немного меняется. VM - по сути, срез модели для отображения. В задачи презентера теперь не будет входить передача данных в подчиненные вьюхи, а только создание биндингов к нужным VM, привязка биндингов к вьюхам и обработка событий. И мы в принципе можем отказаться от общего презентера, т.к. при создании очередного контрола можно просто передавать в него нужную VM, а остальное он сделает сам. Правда в сложных случаях нам все еще будет нужен агрегатор для событий, особенно если события от разных контролов взаимосвязаны или конфиликтуют между собой. Эта модель позволяет наиболее гибкое расширение функционала и массштабирование приложения. 4. Заключение Несмотря на то, что я рассматривал каждую модель по отдельности, на самом деле они плавно вытекают одна из другой по мере усложнения приложения и на практике в чистом виде ни одна из рассмотренных моделей практически не встречается и даже простое с виду приложение может требовать сложных комбинированных решений и наоборот. Просто следуйте логике, здравому смыслу и принципу - "проще - лучше". Ну соответственно при нажатии на кнопку Search я не могу обновить UI, неужели придется в Presenter пробрасывать сам TextBox в ICustomer и вызывать BeginInvoke? Или я просто чего-то недопонимаю в реализации MVP? По предложенным мной вариантам, вы используете второй вариант, и единственно чего в нем не хватает - это события в CustomerPresenter о том что поиск завершен и можно забирать данные для отображения, на которое сможет подписаться форма. Или определить в ICustomer и реализовать в форме Customer метод DataUpdate (или переопределить метод Control.Update формы) и вызывать его из CustomerPresenterкогда поиск завершен и к результатам можно получить доступ из основного потока. краткий алгоритм: кнопкой (или другим действием) на форме Customer передаем запрос на поиск в CustomerPresenter. CustomerPresenter передает запрос в модель модель активирует поиск отдельным потоком. Компоненты основного потока с этого момента свободны для другой полезной работы. посик завершен, результаты загружены в модель. Модель активирует событие о том, что поиск завершен. CustomerPresenter получает событие о завершении поиска, получает результаты поиска из модели и: вызывает метод UpdateData (примерное название) у формы и передает результаты поиска в параметрах. активирует событие завершения длительной операции Customer, получает результаты поиска у CustomerPresenter, как именно зависит от предыдущего пункта, и отображает их. Простоя в работе интерфейса во время поиска нет. Можно и по имеющемуся сценарию: кнопкой (или другим действием) на форме Customer передаем запрос на поиск в CustomerPresenter. CustomerPresenter в отдельном потоке вызывает метод поиска в модели. Компоненты основного потока с этого момента свободны для другой полезной работы. После завершения в потоке поиска с помощью Invoke: вызываем метод UpdateData (примерное название) у формы и передает результаты поиска в параметрах. активируем событие завершения длительной операции Customer, получает результаты поиска у CustomerPresenter, как именно зависит от предыдущего пункта, и отображает их. Еще можно завернуть длинный поиск в асинхронный метод, и уже внутри ожидать завершения потока поиска без подвешивания интерфейса, но этот вариант я только в теории представляю, руками не делал.

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

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