Как вызывать Application.Run () для главного докладчика приложения MVP WinForms? - PullRequest
4 голосов
/ 03 мая 2010

Я учусь применять MVP к простому приложению WinForms (только одна форма) в C # и столкнулся с проблемой при создании основного докладчика в static void Main(). Является ли хорошей идеей предоставить представление от докладчика, чтобы предоставить его в качестве параметра для Application.Run ()?

В настоящее время я реализовал подход, который позволяет мне не предоставлять представление как свойство Presenter:

    static void Main()
    {
        IView view = new View();
        Model model = new Model();
        Presenter presenter = new Presenter(view, model);
        presenter.Start();
        Application.Run();
    }

Методы запуска и остановки в Presenter:

    public void Start()
    {
        view.Start();
    }

    public void Stop()
    {
        view.Stop();
    }

Методы запуска и остановки в представлении (форма Windows):

    public void Start()
    {
        this.Show();
    }

    public void Stop()
    {
        // only way to close a message loop called 
        // via Application.Run(); without a Form parameter
        Application.Exit();
    }

Вызов Application.Exit () выглядит неэффективным способом закрытия формы (и приложения). Другой альтернативой может быть предоставление представления как открытого свойства Presenter для вызова Application.Run () с параметром Form.

    static void Main()
    {
        IView view = new View();
        Model model = new Model();
        Presenter presenter = new Presenter(view, model);
        Application.Run(presenter.View);
    }

Методы запуска и остановки в Presenter остаются прежними. Дополнительное свойство добавляется для возврата представления в виде формы:

    public void Start()
    {
        view.Start();
    }

    public void Stop()
    {
        view.Stop();
    }

    // New property to return view as a Form for Application.Run(Form form);
    public System.Windows.Form View
    {
        get { return view as Form(); }
    }

Методы «Пуск» и «Стоп» в представлении (форма Windows) будут записаны следующим образом:

    public void Start()
    {
        this.Show();
    }

    public void Stop()
    {
        this.Close();
    }

Кто-нибудь может подсказать, какой подход лучше и почему? Или есть еще лучшие способы решить эту проблему?

Ответы [ 4 ]

9 голосов
/ 05 мая 2010

А как насчет следующего:

// view
public void StartApplication() // implements IView.StartApplication
{ 
    Application.Run((Form)this);
}

// presenter
public void StartApplication()
{
    view.StartApplication();
}

// main
static void Main()     
{     
    IView view = new View();     
    Model model = new Model();     
    Presenter presenter = new Presenter(view, model);     
    presenter.StartApplication();     
}     

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

5 голосов
/ 05 мая 2010

Я бы пошел на второй подход. Вы также можете избавиться от дополнительного свойства, просто приведя view к форме в пустом Main, так как вы знаете, что это форма в любом случае в этот момент (я не вижу причин делать ее более общей, так как она просто запускает приложение winform). )

Application.Run(view as Form);
1 голос
/ 06 мая 2010

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

Точный подход к регистрации и использованию действия выхода из приложения будет зависеть от используемого вами механизма IoC (например, поиск службы и / или внедрение зависимости). Поскольку вы не упомянули, каким может быть ваш текущий подход к IoC, вот пример, который не зависит от каких-либо конкретных структур IoC:

internal static class Program
{
    [STAThread]
    private static void Main()
    {
        ApplicationActions.ExitApplication = Application.Exit;

        MainPresenter mainPresenter = new MainPresenter(new MainView(), new Model());
        mainPresenter.Start();

        Application.Run(); 
    }
}

public static class ApplicationActions
{
    public static Action ExitApplication { get; internal set; }
}

public class MainPresenter : Presenter
{
    //...

    public override void Stop()
    {
        base.Stop();

        ApplicationActions.ExitApplication();
    }
}

Этот базовый подход может быть легко адаптирован к вашему предпочтительному подходу IoC. Например, если вы используете локатор службы, вы, вероятно, захотите удалить хотя бы установщик в свойстве ApplicationActions.ExitApplication и вместо этого сохранить делегат в локаторе службы. Если бы получатель ExitApplication оставался, он предоставил бы простой фасад для извлечения экземпляра локатора службы. e.g.:

public static Action ExitApplication
{
    get
    {
        return ServiceLocator.GetInstance<Action>("ExitApplication");
    }
}
0 голосов
/ 13 июня 2019

Вы можете сделать это сотнями способов, чтобы достичь конечной цели разделения интересов. Здесь нет жестких и быстрых правил, основная идея заключается в том, что докладчик имеет дело с логикой представления представления, в то время как представление имеет только тупое знание о своих собственных классах и материалах, специфичных для GUI. Некоторые способы, которыми я могу придумать (в широком смысле):

1) Просматривайте все с нуля и дайте ему решать, как его вести. Вы начинаете как, new View().Start();

// your reusable MVP framework project 
public interface IPresenter<V>
{
    V View { get; set; }
}
public interface IView<P>
{
    P Presenter { get; }
}
public static class PresenterFactory
{
    public static P Presenter<P>(this IView<P> view) where P : new()
    {
        var p = new P();
        (p as dynamic).View = view;
        return p;
    }
}

// your presentation project
public interface IEmployeeView : IView<EmployeePresenter>
{
    void OnSave(); // some view method
}
public class EmployeePresenter : IPresenter<IEmployeeView>
{
    public IEmployeeView View { get; set; } // enforced

    public void Save()
    {
        var employee = new EmployeeModel
        {
            Name = View.Bla // some UI element property on IEmployeeView interface
        };
        employee.Save();
    }
}

// your view project
class EmployeeView : IEmployeeView
{
    public EmployeePresenter Presenter { get; } // enforced

    public EmployeeView()
    {
        Presenter = this.Presenter(); // type inference magic
    }

    public void OnSave()
    {
        Presenter.Save();
    }
}

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

// your reusable MVP framework project 
public interface IPresenter<P, V> where P : IPresenter<P, V> where V : IView<P, V>
{
    V View { get; set; }
}
public interface IView<P, V> where P : IPresenter<P, V> where V : IView<P, V>
{
    P Presenter { get; }
}
public static class PresenterFactory
{
    public static P Presenter<P, V>(this IView<P, V> view)
        where P : IPresenter<P, V>, new() where V : IView<P, V>
    {
        return new P { View = (V)view };
    }
}

// your presentation project
public interface IEmployeeView : IView<EmployeePresenter, IEmployeeView>
{
    //...
}
public class EmployeePresenter : IPresenter<EmployeePresenter, IEmployeeView>
{
    //...
}

Недостатки

  • взаимодействие между формами менее интуитивно для меня.

Шаги:

  • агрегат IEmployeeView
  • создание экземпляра презентатора путем вызова PresenterFactory и передачи this из конструктора представления
  • убедитесь, что события представления связаны с соответствующими методами презентатора
  • начать, как new EmployeeView()....

2) Ведущий запускает вещи и позволяет ему решать их вид. Вы начинаете как, new Presenter().Start();

В этом подходе презентатор создает свое собственное представление (например, подход 1) посредством некоторой инъекции зависимостей или около того, или представление может быть передано в конструктор презентатора. Э.Г.

// your reusable MVP framework project 
public abstract class IPresenter<V> // OK may be a better name here
{
    protected V View { get; }

    protected IPresenter()
    {
        View = ...; // dependenchy injection or some basic reflection, or pass in view to ctor
        (View as dynamic).Presenter = this;
    }
}
public interface IView<P>
{
    P Presenter { get; set; }
}

// your presentation project
public interface IEmployeeView : IView<EmployeePresenter>
{
    void OnSave(); // some view method
}
public class EmployeePresenter : IPresenter<IEmployeeView>
{
    public void Save()
    {
        var employee = new EmployeeModel
        {
            Name = View.Bla // some UI element property on IEmployeedView interface
        };
        employee.Save();
    }
}

// your view project
class EmployeeView : IEmployeeView
{
    public EmployeePresenter Presenter { get; set; } // enforced

    public void OnSave()
    {
        Presenter.Save();
    }
}

Шаги:

  • агрегат IEmployeeView
  • гарантирует, что события представления связаны с соответствующими методами презентатора
  • начать, как new EmployeePresenter(....

3) На основе событий, стиль наблюдателя

Здесь вы можете либо инкапсулировать презентатор в представлении (создание экземпляра презентатора в виде), например, подход 1, либо инкапсулировать представление в презентаторе (экземпляр представления в презентаторе), как подход 2, но, по моему опыту, последний всегда будет более чистым проектом для работы. Например, из последних:

// your reusable MVP framework project
public abstract class IPresenter<V> where V : IView
{
    protected V View { get; }

    protected IPresenter()
    {
        View = ...; // dependenchy injection or some basic reflection, or pass in view to ctor
        WireEvents();
    }

    protected abstract void WireEvents();
}

// your presentation project
public interface IEmployeeView : IView
{
    // events helps in observing
    event Action OnSave; // for e.g.
}
public class EmployeePresenter : IPresenter<IEmployeeView>
{
    protected override void WireEvents()
    {
        View.OnSave += OnSave;
    }

    void OnSave()
    {
        var employee = new EmployeeModel
        {
            Name = View.Bla // some UI element property on IEmployeedView interface
        };
        employee.Save();
    }
}

// your view project
class EmployeeView : IEmployeeView
{
    public event Action OnSave;
    void OnClicked(object sender, EventArgs e) // some event handler
    {
        OnSave();
    }
}
// you kick off like new EmployeePresenter()....

Недостаток:

  • Вы должны связать события как на стороне просмотра, так и на стороне докладчика - удвойте работу

Шаги:

  • агрегат IEmployeeView
  • гарантирует, что события iview вызываются из методов обработчика событий представления
  • обеспечить инициализацию участников события iview от докладчика
  • начать, как new EmployeePresenter()....

Ограничения языка иногда усложняют шаблоны проектирования. Например, если бы множественное наследование было возможно в C #, это был только вопрос наличия абстрактного базового класса представления со всеми деталями реализации, кроме специфических компонентов пользовательского интерфейса, которые затем могли быть реализованы классом представления. Нет ведущих, классический полиморфизм и просто мертвецы! К сожалению, это невозможно, поскольку большинство классов представлений в .NET (например, Form в WinForms) уже наследуются от класса суперпредставлений. Поэтому мы должны реализовать интерфейс и перейти к композиции. Кроме того, C # не позволяет иметь закрытых членов в реализации интерфейса, поэтому мы вынуждены сделать все члены, указанные в IEmployeeView, общедоступными, что нарушает естественные правила инкапсуляции класса представления (т. Е. Другие представления в проекте представления могут видеть детали EmployeeView не имеют к ним отношения). В любом случае, используя мощные методы расширения C #, можно использовать гораздо более простой, но очень ограниченный подход.

4) Метод расширения подхода

Это просто глупо.

// your presentation project
public interface IEmployeeView
{
    void OnSave(); // some view method
}
public static class EmployeePresenter // OK may need a better name
{
    public void Save(this IEmployeeView view)
    {
        var employee = new EmployeeModel
        {
            Name = view.Bla // some UI element property on IEmployeedView interface
        };
        employee.Save();
    }
}

// your view project
class EmployeeView : IEmployeeView
{       
    public void OnSave()
    {
        this.Save(); // that's it. power of extensions.
    }
}

Недостатки:

  • довольно непригодно для чего-либо сложного

Шаги:

  • агрегат IEmployeeView
  • обеспечить this.... метод расширения вызывается из событий просмотра
  • начать все, позвонив по новому View ...

Из всех 2 и 3 выглядят лучше для меня.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...