如何为MVP WinForms应用程序的主要演示者调用Application.Run()?

时间:2010-05-03 10:40:11

标签: c# winforms mvp

我正在学习在C#中将MVP应用到一个简单的WinForms应用程序(只有一个表单),并在static void Main()中创建主要演示者时遇到问题。从Presenter公开View以将其作为参数提供给Application.Run()是一个好主意吗?

目前,我已经实现了一种方法,允许我不将View作为Presenter的属性公开:

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

Presenter中的Start和Stop方法:

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

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

View中的Start和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()调用似乎是一种关闭Form(和应用程序)的不雅方式。另一种方法是将View作为Presenter的公共属性公开,以便使用Form参数调用Application.Run()。

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

Presenter中的Start和Stop方法保持不变。添加了一个附加属性以将视图作为表单返回:

    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(); }
    }

View中的Start和Stop方法(Windows窗体)将按如下方式编写:

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

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

有人能建议哪种方法更好,为什么?或者有更好的方法来解决这个问题?

4 个答案:

答案 0 :(得分:9)

以下内容如何:

// 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();     
}     

这样,您不需要将视图暴露给外部。此外,视图和演示者都知道这个视图已经作为“主要形式”开始,这可能是一个有用的信息。

答案 1 :(得分:5)

我会选择第二种方法。 您还可以通过简单地将视图转换为void Main中的表单来消除额外的属性,因为您知道它在那时仍然是一个表单(我认为没有理由使它更通用,因为它只是启动winform app )

Application.Run(view as Form);

答案 2 :(得分:1)

如果您允许多种方式退出应用程序(例如:退出的菜单项),或者如果您在某些条件下阻止关闭应用程序,则事情会变得复杂一些。在任何一种情况下,应用程序关闭的实际调用通常应该从演示者代码调用,而不是简单地关闭具体视图。这可以通过使用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属性上的setter,并将该委托存储在服务定位器中。如果要保留ExitApplication getter,它将为服务定位器实例检索器提供简单的外观。 e.g:

public static Action ExitApplication
{
    get
    {
        return ServiceLocator.GetInstance<Action>("ExitApplication");
    }
}

答案 3 :(得分:0)

您可以用一百种方法来实现关注点可分离性的最终目标。这里没有硬性规定,基本思想是演示者处理视图的表示逻辑,而视图仅具有其自己的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#中可能实现多重继承,那么只有一个抽象的基本视图类具有所有实现细节,只是可以由视图类实现的UI特定组件除外。没有演示者,经典的多态性和简单的死法!不幸的是,这是不可能的,因为.NET中的大多数视图类(例如WinForms的Form)已经继承自超级视图类。因此,我们必须实现一个接口并进行合成。另外,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....扩展方法
  • 通过调用新的“视图”来开始事情。

在所有2和3中,对我来说看起来更好。