Winforms 程序采用 MVP 的一点总结
2025-07-01 20:55:10

#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 中订阅通知
  1. 使用 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 的依赖注入?