Winforms 程序采用 MVP 的一点总结
2024-04-06 16:39:11

MVP的两种实现方式

MVP有两种变体:Passive ViewSupervising Controller。两者的主要区别就是ViewModel是否直接交流。

被动视图(Passive View)

ViewModel不知道彼此。Model的变化要告知Presenter,再由Presenter来改变View
优点是ViewModel解耦了。缺点是Presenter会比较重,数据更新需要手动完成,这就意味着放弃了数据绑定功能。

监督控制器(Supervising Controller)

View可以知道Model的存在。让Model通过事件等方式发出数据变化的通知,View订阅Model的变化。
这主要是利用了语言或框架的特性让View直接感知Model的变化,而不用经过Presenter,这样可以减少代码量,缺点是ViewModel耦合了。

M、V、P之间的关系

先看图

为 V 定义一个接口,接口中暴露的方法就是为 P 提供的。这么做的目的是为了让 P 不知道 V 的存在,从而让 P 仅依赖接口,这样更容易测试 P。
V 和 P 是 1:1 的关系。
P 和 M 是 1:N 的关系。

让 View 独立

先看一个典型的ViewPresenter连接的地方

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Presenter
{
public Presenter(IView view)
{
_view = view;
}
}

public class View : IView
{
public View()
{
_presenter = new Presenter(this);
}
}

当用户触发一个 UI 行为,比如点击鼠标时,如何让Presenter响应呢?基本上就两种方式:

  1. View直接调用Presenter中的方法。但这会产生耦合。
  2. View利用事件等机制实现广播,让Presenter去订阅View产生的通知。这样View就是完全独立的。但需要在释放时取消订阅事件,这比直接调用Presenter的方法多了一些步骤。

普遍共识是采用第二种方式,让View不知道Presenter的存在。但这只是理想情况,现实开发中还有很多复杂性,所以还是得看情况决定用哪种方式。

但是上面的例子View依然还是知道如何创建Presenter

1
2
3
4
5
6
7
public class View : IView
{
public View()
{
_presenter = new Presenter(this);
}
}

此时你有三种选择:

  1. 在类的外部将ViewPresenter连接起来(订阅通知)。
  2. 创建一个PresenterFactory工厂类,构造过程交给它。
    1
    2
    mPresenter = PresenterFactory.Create<MyPresenter>();
    mPresenter.View = this; // 在 seter 中订阅通知
  3. 使用 IoC 容器注入(推荐)。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    public interface IHomeView { }

    public interface IPresenter<TView> {
    TView View { get; set; }
    }

    public class HomeView : Form, IHomeView
    {
    private readonly IPresenter<IHomeView> presenter;

    public HomeView(IPresenter<IHomeView> presenter) {
    this.presenter = presenter;
    InitializeComponent();
    }
    }

至此,View仅仅只是知道一个什么都没用的IPresenter接口,从来不会使用它,持有它只是为了保持Presenter的生命周期。
最后记住在View关闭时通知Presenter互相解除引用。

参考

浅谈 MVC、MVP 和 MVVM 架构模式
MVP 和 MVC 是什么以及有什么区别?
如何在 WinForms 中使用 MVP 的依赖注入?