说说MVP模式
初看MVP模式时被它复杂的包含,继承,接口搞晕,View中有Presenter,Presenter中又有View,View又要抽象出IView,View又调用Presenter的方法,Presenter又调用IView的方法.花了点时间算是搞明白了,这里说说自己的理解.
关于MVP模式文章一搜一大把.建议先看看这些文章.
Artceh
http://www.cnblogs.com/artech/archive/2010/04/12/1710681.html
http://www.cnblogs.com/artech/archive/2010/03/25/1696205.html
Jianqiang Bao
http://www.cnblogs.com/jax/archive/2009/10/09/1579404.html
Microsoft
理解MVP模式,其实很简单
可以把V和P的交互想象成一个HTTTP请求,View为客户端,Presenter相当于服务器端,处理流程:
1.View发生一些事件,一般就是页面事件,按钮点击事件等.
2.View请求Presenter处理.
3.Presenter整合调度Model的方法处理.
4.Presenter调用View的方法作为回应.
流程就这么简单,搞明白这个基本就差不多了.
开发MVP模式的一般流程
结合实例总结一下用MVP开发的步骤.例子很简单,就是显示一个List.可以显示全部员工,也可以显示属于某个项目的员工.
1.抽象出IView.根据页面功能抽象.可分为两个部分.
请求部分,对应Winform,Webform的各种页面,控件事件,参考Artceh的文章,请求部分应该是自定义事件.在这个例子中,没有按钮点击,也没有下拉框改变,只有页面的load事件,所以请求部分就是一个ViewLoad.
响应部分,是提供给Presenter操作View的,例如显示数据,显示错误提示,清空控件状态等.在这个例子中当然就是把一些数组show出来,考虑到将来有可能有多个控件的数据绑定,所以把数据绑定放在一个方法中,直接this.DataBind()就可以,不用每个控件都绑定一次,简单点的话不要这个PageDataBind也可以.
为了更清晰,我用partial 类,接口把每个关注点分开了.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | //IView namespace WebAppMVP.IView { /// <summary> /// 请求部分,对应各种事件,页面事件,按钮单击事件,下拉框改变事件等等 /// 要在View中封装处理一下,不能直接把对应的事件丢给Presenter /// 所以下面的事件参数为,ListEmployeeEventArgs /// </summary> public partial interface IListEmployeeView { event EventHandler<ListEmployeeEventArgs> ViewLoad; } /// <summary> /// 回应部分 /// </summary> public partial interface IListEmployeeView { void ShowEmployee(ListEmployeeViewModel vm); void PageDataBind(); } } |
2自定义请求参数.
请求部分是一个自定义事件,所以ListEmployeeEventArgs是这个事件的自定义参数,通过封装这些参数,就可以把与业务无关的代码过滤掉.在这个页面功能中,因为需要显示属于某个项目的员工,所以参数当然就是ProjectId(Name是多余的,我不想再查询一次,其实只需ProjectId即可).
1 2 3 4 5 6 7 8 9 | //EventArgs namespace WebAppMVP.IView.EventArgs { public class ListEmployeeEventArgs : System.EventArgs { public int ? ProjectId { get ; set ; } public string Name { get ; set ; } } } |
1 2 3 4 5 6 7 8 9 10 | //ViewModel namespace WebAppMVP.ViewModel { public class ListEmployeeViewModel { public string ProjectName { get ; set ; } public Employee[] Employees { get ; set ; } } } |
4.Presenter.
在Presenter中用构造函数注入一个IView.
订阅IView的事件.
事件处理方法中包含两个部分,1根据参数处理业务,2调用IView定义的方法作为回应
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | namespace WebAppMVP.Presenter { public partial class ListEmployeePresenter { IListEmployeeView m_IView { get ; set ; } MockData mockData = new MockData(); public ListEmployeePresenter(IListEmployeeView iview) { this .m_IView = iview; //订阅事件,即View那边发生对应的事件,这里负责处理,责任很大,包括业务逻辑(如果有另外的业务逻辑层,就服务整合和调度) //还要负责如何显示数据(只负责调用IView的方法,具体实现在View中), this .m_IView.ViewLoad += (sender, args) => { #region 业务逻辑 ListEmployeeViewModel vm = new ListEmployeeViewModel(); if (args.ProjectId.HasValue) { vm.ProjectName = args.Name; vm.Employees = mockData.Employees.Where(e => e.Projects.Any(p => p.Id == args.ProjectId.Value)).Take(10).ToArray(); } else { vm.ProjectName = string .Empty; vm.Employees = mockData.Employees.Take(10).ToArray(); } #endregion #region 显示数据,作为回应 this .m_IView.ShowEmployee(vm); this .m_IView.PageDataBind(); #endregion }; } } } |
5.View.
这里我也分为三个部分.
初始化部分在View中继承IView,实例化一个Presenter.
1 2 3 4 5 6 7 8 9 10 11 12 13 | /// <summary> /// 初始化部分,每个页面基本雷同 /// </summary> public partial class ListEmployeeView : System.Web.UI.Page, IListEmployeeView { public event EventHandler<ListEmployeeEventArgs> ViewLoad; private ListEmployeePresenter m_Presenter; protected void Page_Init( object sender, EventArgs e) { m_Presenter = new ListEmployeePresenter( this ); } } |
请求部分在对应的页面,控件事件中准备XXXEventArgs参数,触发方法IView的自定义事件.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | /// <summary> /// 请求部分,这里的职责就是监视页面发生什么事件,出现什么状况,准备对应的参数,请求Presenter处理它 /// </summary> public partial class ListEmployeeView { protected void Page_Load( object sender, EventArgs e) { #region 准备数据,参数,UI逻辑,实现了将这些UI相关代码与真正业务逻辑隔离 ListEmployeeEventArgs args = new ListEmployeeEventArgs(); if (Request.QueryString.AllKeys.Contains( "id" )) { var id = 0; if ( int .TryParse(Request.QueryString[ "id" ], out id)) { args.ProjectId = id; args.Name = HttpUtility.UrlDecode(Request.QueryString[ "name" ]); } else { args.ProjectId = null ; args.Name = string .Empty; } } else { args.ProjectId = null ; args.Name = string .Empty; } #endregion //Presenter那边已经订阅了事件,触发事件,请求Presenter处理 ViewLoad(sender, args); } } |
响应部分,实现IView中定义的响应方法.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | namespace WebAppMVP.View { /// <summary> /// 响应部分,这里对应的方法的职责非常简单--显示数据,信息等等,这里不知道Presenter,没有业务逻辑. /// </summary> public partial class ListEmployeeView { public void ShowEmployee(ListEmployeeViewModel vm) { this .rpEmployee.DataSource = vm.Employees; this .Title = string .IsNullOrEmpty(vm.ProjectName) ? "员工列表" : vm.ProjectName + "的员工列表" ; this .lbProjectName.Text = this .Title; } public void PageDataBind() { this .DataBind(); } } } |
1 | 就这样,基于MVP的开发就完成了. |
简单总结
1.首先发现是,有很多XXX.cs,也就是说类文件和类,接口会多好几倍.
2.功能,职责很清晰,可测试性也大大提高.
3.跟传统开发差别较大,相对有点不好理解,相信这也是很少人用的原因
4.疑问1,没有用来做过实际项目,不知道应对需求变更方面能力如何?
5.疑问2,可以看到上面基本没有提到M,基本是PV的交互,感觉MVP中的M作用相对较弱,是一个类似Domain Service的角色,小一点的系统中M完全可以不要(不过小系统也没必要用MVP了).
the end
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· .NET周刊【3月第1期 2025-03-02】
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· [AI/GPT/综述] AI Agent的设计模式综述