Model-View-Presenter у WinForms


90

Я намагаюся впровадити метод MVP вперше, використовуючи WinForms.

Я намагаюся зрозуміти функції кожного шару.

У моїй програмі у мене є кнопка графічного інтерфейсу, яка при натисканні на неї відкриває вікно діалогового вікна openfile.

Отже, використовуючи MVP, графічний інтерфейс обробляє подію натискання кнопки, а потім викликає презентатор.openfile ();

Якщо у презентаторі.openfile (), чи слід це делегувати відкриття цього файлу на шар моделі, або оскільки немає даних або логіки для обробки, чи слід просто діяти за запитом і відкрити вікно діалогового вікна openfile?

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

Добре, після прочитання MVP, я вирішив реалізувати пасивний погляд. Фактично я буду мати купу елементів керування на Winform, якими буде керувати Ведучий, а потім завдання, делеговані Моделі. Мої конкретні моменти нижче:

  1. Коли вінформ завантажується, він повинен отримати деревоподібний вигляд. Чи правильно я вважаю, що подання, отже, повинно викликати такий метод, як: presenter.gettree (), це, в свою чергу, делегує моделі, яка отримає дані для перегляду дерева, створить їх і налаштує, поверне в ведучий, який у свою чергу перейде до подання, яке потім просто призначить його, скажімо, панелі?

  2. Чи буде це однаково для будь-якого елемента керування даними на Winform, оскільки я також маю datagridview?

  3. Мій додаток має ряд класів моделей з однаковим складанням. Він також підтримує архітектуру плагінів із плагінами, які потрібно завантажувати під час запуску. Чи просто вигляд викликав би метод презентатора, який, у свою чергу, викликав би метод, який завантажує плагіни та відображає інформацію у поданні? Який рівень тоді контролюватиме посилання на плагіни. У поданні міститься посилання на них чи на ведучого?

  4. Чи правильно я вважаю, що подання має обробляти все, що стосується презентації, від кольору вузла дерева до розміру сітки даних тощо?

Я думаю, що це моє головне занепокоєння, і якщо я зрозумію, яким повинен бути потік для них, я думаю, що я буду в порядку.


Це посилання lostechies.com/derekgreer/2008/11/23/… пояснює деякі стилі MVP. Це може виявитися корисним на додаток до чудової відповіді Йоганна.
ak3nat0n

Відповіді:


123

Це мій скромний погляд на 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. Коли вінформ завантажується, він повинен отримати вигляд дерева. Чи правильно я вважаю, що подання, отже, повинно викликати такий метод, як: 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 пункти складності", оскільки подання насправді не знає, яка подія перегляду відповідає якому методу ведучого . У контракті цього не зазначено.
Йоганн Герелл,

5
@lejon: Якщо, з іншого боку, подання викриває лише фактичну подію, то сам ведучий (хто знає, що він хоче робити, коли відбувається подія перегляду) просто підписується на нього, щоб зробити правильну справу. Це лише "1 бал складності", що в моїй книзі вдвічі перевершує "2 бали складності". Взагалі кажучи, менше зчеплення означає менші витрати на технічне обслуговування протягом періоду проекту.
Йоганн Герелл,

9
Я теж схильний використовувати інкапсульований презентатор, як це пояснюється у цьому посиланні lostechies.com/derekgreer/2008/11/23/…, де подання є єдиним власником ведучого.
ak3nat0n

3
@ ak3nat0n: Стосовно трьох стилів MVP, пояснених у наведеному вами посиланні, я вважаю, що ця відповідь Йоганна може бути найбільш тісно узгоджена з третім стилем, який називається Стиль спостереження ведучого : "Перевага стилю спостереження ведучого полягає в тому, що це повністю відокремлює знання доповідача від подання, роблячи вигляд менш сприйнятливим до змін у доповідачі ".
DavidRR

11

Ведучий, який містить всю логіку у поданні, повинен відповісти на натиснуту кнопку, як каже @JochemKempe . На практиці виклик обробника події натискання кнопки presenter.OpenFile(). Тоді ведучий може визначити, що слід робити.

Якщо він вирішить, що користувач повинен вибрати файл, він передзвонить у подання (через інтерфейс подання) і дозволить поданню, яке містить усі технічні характеристики інтерфейсу, відобразити OpenFileDialog. Це дуже важлива відмінність у тому, що ведучому не можна дозволяти виконувати операції, пов’язані із використовуваною технологією інтерфейсу користувача.

Потім вибраний файл буде повернуто ведучому, який продовжує свою логіку. Це може залучати будь-яку модель чи службу, яка повинна обробляти файл.

Основною причиною використання шаблону MVP, imo, є відокремлення технології UI від логіки подання. Таким чином, ведучий організовує всю логіку, тоді як подання тримає її відокремленою від логіки інтерфейсу. Це має дуже приємний побічний ефект, завдяки якому ведучий повністю перевіряється.

Оновлення: оскільки презентатор є втіленням логіки, знайденої в одному конкретному поданні , відносини погляд-презентатор є IMO відносинами один до одного. І для всіх практичних цілей один екземпляр подання (скажімо форма) взаємодіє з одним екземпляром презентатора, а один екземпляр презентації взаємодіє лише з одним екземпляром подання.

Тим не менш, у моїй реалізації MVP з WinForms, ведучий завжди взаємодіє з видом через інтерфейс, що представляє можливості інтерфейсу користувача для представлення. Немає обмежень щодо того, яке представлення реалізує цей інтерфейс, отже різні "віджети" можуть реалізовувати один і той же інтерфейс подання та повторно використовувати клас презентатора.


Дякую. Отже, у методі презентатора.OpenFile () він не повинен мати коду для відображення діалогового вікна openfile? Натомість він повинен повернутися у вигляд, щоб показати це вікно?
Даррен Янг

4
Правильно, я ніколи не дозволяв би ведучому відкривати діалогові вікна безпосередньо, оскільки це порушить ваші тести. Або розвантажте це для подання, або, як я це робив у деяких сценаріях, майте окремий клас "FileOpenService", який обробляє фактичну діалогову взаємодію. Таким чином ви можете підробити службу відкриття файлів під час тестів. Поміщення такого коду в окрему службу може дати вам приємні побічні ефекти для повторного використання :)
Пітер Ліллевольд,

2

Доповідач повинен діяти відповідно до запиту і показати вікно діалогового вікна openfile, як ви запропонували. Оскільки дані від моделі не потрібні, ведучий може і повинен обробляти запит.

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


1
Дякую за відповідь. Крім того, чи хочете ви мати одного ведучого для перегляду? І цей ведучий або обробляє запит, або якщо потрібні дані, то він делегує будь-яку кількість класів моделей, які діють за конкретними запитами? Це правильний спосіб? Знову дякую.
Даррен Янг

3
У View є один ведучий, але ведучий може мати кілька поглядів.
JochemKempe
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.