Модель-представление-презентатор в WinForms

91

Я впервые пытаюсь реализовать метод MVP, используя WinForms.

Я пытаюсь понять функцию каждого слоя.

В моей программе у меня есть кнопка графического интерфейса, которая при нажатии открывает окно openfiledialog.

Таким образом, используя MVP, графический интерфейс обрабатывает событие нажатия кнопки, а затем вызывает presenter.openfile ();

Должен ли он в presenter.openfile () делегировать открытие этого файла на уровень модели, или, если нет данных или логики для обработки, следует ли просто действовать по запросу и открывать окно openfiledialog?

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

Хорошо, прочитав о MVP, я решил реализовать пассивное представление. Фактически у меня будет набор элементов управления в Winform, которые будут обрабатываться Presenter, а затем задачи, делегированные модели (ам). Мои конкретные замечания приведены ниже:

  1. Когда winform загружается, он должен получить древовидное представление. Правильно ли я полагаю, что представление должно поэтому вызывать такой метод, как: presenter.gettree (), это, в свою очередь, делегирует модель, которая получит данные для древовидного представления, создаст их и настроит, вернет их в ведущий, который, в свою очередь, перейдет к представлению, которое затем просто назначит его, скажем, панели?

  2. Будет ли это то же самое для любого элемента управления данными в Winform, поскольку у меня также есть datagridview?

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

  4. Правильно ли я считаю, что представление должно обрабатывать все, что касается презентации, от цвета узла древовидного представления до размера сетки данных и т. Д.?

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

Даррен Янг
источник
Эта ссылка lostechies.com/derekgreer/2008/11/23/… объясняет некоторые стили MVP. Это может оказаться полезным в дополнение к прекрасному ответу Иоганна.
ak3nat0n 01

Ответы:

125

Это мой скромный взгляд на MVP и ваши конкретные проблемы.

Во-первых , все, с чем пользователь может взаимодействовать или просто показывать, является представлением . Законы, поведение и характеристики такого вида описываются интерфейсом . Этот интерфейс может быть реализован с использованием пользовательского интерфейса WinForms, пользовательского интерфейса консоли, веб-интерфейса или даже без пользовательского интерфейса (обычно при тестировании докладчика) - конкретная реализация просто не имеет значения, если она подчиняется законам своего интерфейса представления. .

Во-вторых , представление всегда контролируется ведущим . Законы, поведение и характеристики такого ведущего также описываются интерфейсом . Этот интерфейс не заинтересован в конкретной реализации представления, пока он подчиняется законам своего интерфейса представления.

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

Последствия Третьего :

  • У докладчика нет методов, которые может вызывать представление, но в представлении есть события, на которые докладчик может подписаться.
  • Ведущий знает свое мнение. Я предпочитаю делать это с помощью внедрения конструктора в конкретный презентатор.
  • Представление не знает, какой ведущий им управляет; просто никогда не будет предоставлен ни один докладчик.

Для вашей проблемы приведенное выше может выглядеть в несколько упрощенном коде:

interface IConfigurationView
{
    event EventHandler SelectConfigurationFile;

    void SetConfigurationFile(string fullPath);
    void Show();
}

class ConfigurationView : IConfigurationView
{
    Form form;
    Button selectConfigurationFileButton;
    Label fullPathLabel;

    public event EventHandler SelectConfigurationFile;

    public ConfigurationView()
    {
        // UI initialization.

        this.selectConfigurationFileButton.Click += delegate
        {
            var Handler = this.SelectConfigurationFile;

            if (Handler != null)
            {
                Handler(this, EventArgs.Empty);
            }
        };
    }

    public void SetConfigurationFile(string fullPath)
    {
        this.fullPathLabel.Text = fullPath;
    }

    public void Show()
    {
        this.form.ShowDialog();        
    }
}

interface IConfigurationPresenter
{
    void ShowView();
}

class ConfigurationPresenter : IConfigurationPresenter
{
    Configuration configuration = new Configuration();
    IConfigurationView view;

    public ConfigurationPresenter(IConfigurationView view)
    {
        this.view = view;            
        this.view.SelectConfigurationFile += delegate
        {
            // The ISelectFilePresenter and ISelectFileView behaviors
            // are implicit here, but in a WinForms case, a call to
            // OpenFileDialog wouldn't be too far fetched...
            var selectFilePresenter = Gimme.The<ISelectFilePresenter>();
            selectFilePresenter.ShowView();
            this.configuration.FullPath = selectFilePresenter.FullPath;
            this.view.SetConfigurationFile(this.configuration.FullPath);
        };
    }

    public void ShowView()
    {
        this.view.SetConfigurationFile(this.configuration.FullPath);
        this.view.Show();
    }
}

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

На ваши вопросы:

1. Когда winform загружается, он должен получить древовидное представление. Правильно ли я полагаю, что представление должно поэтому вызывать такой метод, как: presenter.gettree (), это, в свою очередь, делегирует модель, которая получит данные для древовидного представления, создаст их и настроит, вернет их в ведущий, который, в свою очередь, перейдет к представлению, которое затем просто назначит его, скажем, панели?

Я бы позвонил IConfigurationView.SetTreeData(...)из IConfigurationPresenter.ShowView(), прямо перед звонком вIConfigurationView.Show()

2. Будет ли это то же самое для любого элемента управления данными в Winform, поскольку у меня также есть datagridview?

Да, я бы позвонил IConfigurationView.SetTableData(...)по этому поводу. Форматирование предоставленных ему данных зависит от представления. Ведущий просто подчиняется контракту представления о том, что ему нужны табличные данные.

3. Мое приложение содержит несколько классов моделей с одной и той же сборкой. Он также поддерживает архитектуру плагинов с плагинами, которые необходимо загружать при запуске. Будет ли представление просто вызывать метод докладчика, который, в свою очередь, вызывает метод, который загружает плагины и отображает информацию в представлении? Какой уровень затем будет управлять ссылками на подключаемые модули. Будет ли представление содержать ссылки на них или на докладчика?

Если плагины связаны с представлениями, то о них должны знать представления, но не докладчик. Если все они касаются данных и модели, то представление не должно иметь к ним никакого отношения.

4. Правильно ли я считаю, что представление должно обрабатывать все, что касается представления, от цвета узла древовидного представления до размера сетки данных и т. Д.?

Да. Думайте об этом как о докладчике, предоставляющем XML, который описывает данные, и представление, которое принимает данные и применяет к ним таблицу стилей CSS. Говоря конкретнее, докладчик может позвонить, IRoadMapView.SetRoadCondition(RoadCondition.Slippery)и представление затем отобразит дорогу красным цветом.

А как насчет данных для узлов, по которым щелкнули?

5. Если, когда я щелкаю по тринодам, должен ли я пройти через конкретный узел к ведущему, а затем, исходя из этого, ведущий определит, какие данные ему нужны, а затем запросит эти данные у модели, прежде чем представить их обратно в представление?

Если возможно, я бы передал все данные, необходимые для представления дерева в виде за один раз. Но если некоторые данные слишком велики для передачи с самого начала или если они динамичны по своей природе и нуждаются в «последнем снимке» из модели (через презентатора), то я бы добавил что-то вроде event LoadNodeDetailsEventHandler LoadNodeDetailsинтерфейса просмотра, чтобы презентатор может подписаться на него, получить сведения об узле LoadNodeDetailsEventArgs.Node(возможно, через его какой-либо идентификатор) из модели, чтобы представление могло обновлять отображаемые сведения об узле при возвращении делегата обработчика событий. Обратите внимание, что для этого могут потребоваться асинхронные шаблоны, если выборка данных может быть слишком медленной для хорошего взаимодействия с пользователем.

Иоганн Герелл
источник
3
Я не думаю, что вам обязательно нужно разделять представление и ведущего. Обычно я разделяю модель и ведущего, чтобы докладчик слушал события модели и действовал соответствующим образом (обновлял представление). Наличие докладчика в представлении упрощает взаимодействие между представителем и докладчиком.
kasperhj
11
@lejon: Вы говорите, что наличие ведущего в поле зрения облегчает общение между представителем и ведущим , но я категорически не согласен. Моя точка зрения такова: когда представление знает о презентаторе, тогда для каждого события представления оно должно решить, какой метод презентатора является правильным для вызова. Это «2 уровня сложности», поскольку представление на самом деле не знает, какое событие представления соответствует какому методу докладчика . В контракте этого не сказано.
Johann Gerell
5
@lejon: если, с другой стороны, представление предоставляет только фактическое событие, то сам ведущий (кто знает, что он хочет делать, когда происходит событие представления) просто подписывается на него, чтобы делать правильные действия. Это всего лишь «1 балл сложности», что в моей книге вдвое лучше, чем «2 балла сложности». Вообще говоря, меньшее количество взаимосвязей означает меньшие затраты на обслуживание в течение всего проекта.
Johann Gerell
9
Я тоже склонен использовать инкапсулированный презентатор, как описано в этой ссылке lostechies.com/derekgreer/2008/11/23/…, в которой представление является единственным держателем презентатора.
ak3nat0n 01
3
@ ak3nat0n: Что касается трех стилей MVP, описанных в предоставленной вами ссылке, я считаю, что этот ответ Иоганна может быть наиболее тесно связан с третьим стилем, который называется Стиль наблюдения за докладчиком : «Преимущество стиля наблюдения за докладчиком заключается в том, что он полностью отделяет информацию о презентаторе от представления, делая представление менее восприимчивым к изменениям внутри докладчика ".
DavidRR
11

Презентатор , который содержит всю логику в представлении, должен реагировать на нажатие кнопки, как говорит @JochemKempe . На практике это вызовы обработчика событий нажатия кнопки presenter.OpenFile(). Затем докладчик может определить, что следует делать.

Если он решает, что пользователь должен выбрать файл, он обращается обратно в представление (через интерфейс представления) и позволяет представлению, которое содержит все технические характеристики пользовательского интерфейса, отображать файл OpenFileDialog. Это очень важное различие в том, что докладчику не должно быть разрешено выполнять операции, связанные с используемой технологией пользовательского интерфейса.

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

Основная причина использования шаблона MVP, imo, заключается в том, чтобы отделить технологию пользовательского интерфейса от логики представления. Таким образом, докладчик организует всю логику, в то время как представление хранит ее отдельно от логики пользовательского интерфейса. Это имеет очень приятный побочный эффект, делая докладчика полностью пригодным для модульного тестирования.

Обновление: поскольку ведущий является воплощением логики, найденной в одном конкретном представлении , отношения между представителем и презентатором являются отношениями IMO один-к-одному. И для всех практических целей один экземпляр представления (скажем, форма) взаимодействует с одним экземпляром презентатора, а один экземпляр презентатора взаимодействует только с одним экземпляром представления.

Тем не менее, в моей реализации MVP с WinForms докладчик всегда взаимодействует с представлением через интерфейс, представляющий возможности пользовательского интерфейса представления. Нет ограничений на то, какое представление реализует этот интерфейс, поэтому разные «виджеты» могут реализовывать один и тот же интерфейс представления и повторно использовать класс презентатора.

Питер Лиллевольд
источник
Благодарю. Значит, в методе presenter.OpenFile () не должно быть кода для отображения openfiledialog? Вместо этого он должен вернуться в представление, чтобы отобразить это окно?
Даррен Янг
4
Правильно, я бы никогда не позволил докладчику открывать диалоговые окна напрямую, так как это нарушит ваши тесты. Либо переложите это на представление, либо, как я сделал в некоторых сценариях, создайте отдельный класс FileOpenService, обрабатывающий фактическое диалоговое взаимодействие. Таким образом можно подделать службу открытия файлов во время тестов. Помещение такого кода в отдельный сервис может дать вам приятные побочные эффекты повторного использования :)
Питер Лиллевольд
2

Докладчик должен действовать в конце запроса, показывая окно openfiledialog, как вы предложили. Поскольку данные от модели не требуются, докладчик может и должен обработать запрос.

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

ЙохемКемпе
источник
1
Спасибо за ответ. Кроме того, у вас будет один ведущий для просмотра? И этот докладчик либо обрабатывает запрос, либо, если требуются данные, делегирует их любому количеству классов модели, которые действуют в соответствии с конкретными запросами? Это правильный путь? Еще раз спасибо.
Даррен Янг
3
Представление имеет одного докладчика, но докладчик может иметь несколько представлений.
JochemKempe