首页 > 浅谈MVP设计模式

浅谈MVP设计模式

  最近公司在做一个医疗项目,使用WinForm界面作为客户端交互界面。在整个客户端解决方案中。使用了MVP模式实现。由于之前没有接触过该设计模式,所以在项目完成到某个阶段时,将使用MVP的体会写在博客里面。

  所谓的MVP指的是Model,View,Presenter。对于一个UI模块来说,它的所有功能被分割为三个部分,分别通过Model、View和Presenter来承载。Model、View和Presenter相互协作,完成对最初数据的呈现和对用户操作的响应,它们具有各自的职责划分。Model可以看成是模块的业务逻辑和数据的提供者;View专门负责数据可视化的呈现,和用户交互事件的响应。一般地,View会实现一个相应的接口;Presenter是一般充当Model和View的纽带。

  其依赖关系为:

  View直接依赖Presenter,即在View实体保存了Presenter的引用; 而Presenter通过依赖View的接口,实现对View的数据推送和数据呈现任务的分配(Presenter处理完业务逻辑之后,在需要展示数据时,通过调用View的接口,将数据推送给View);Model与View之间不存在依赖关系,Model与Presenter之间存在依赖关系。

如下图所示:

MVP模式中有一个比较特殊的地方,就是虽然View有依赖Preserter,但是不应该由View主动的去访问Presenter,View职责:接收用户的的请求,将请求提交给Presenter,在适合的时候展示数据(Presenter处理完请求之后,会主动将数据推送)。如何设计,才能防止View主动访问Presenter呢?通过事件订阅的机制,View的接口中,将所有可能处理的请求做成事件,然后由Presenter去订阅该事件,那么客户端需要处理请求时,就直接调用事件。代码如下:

/// /// 窗体的抽象基类/// public partial class BaseView : DockContent{protected ViewHelper viewHelper;public BaseView(){InitializeComponent();//viewHelper负责动态的创建Presenter,并保存起来。viewHelper = new ViewHelper(this);this.FormClosing += BaseView_FormClosing;}void BaseView_FormClosing(object sender, FormClosingEventArgs e){if (hook != null){hook.UnInstall();}}#region 公共弹窗方法public void ShowInformationMsg(string msg, string caption){if (this.InvokeRequired){this.Invoke(new Action(() =>{MessageBox.Show(msg, caption, MessageBoxButtons.OK, MessageBoxIcon.Information);this.Focus();}));}else{MessageBox.Show(msg, caption, MessageBoxButtons.OK, MessageBoxIcon.Information);this.Focus();}}public void ShowErrorMsg(string msg, string caption){if (this.InvokeRequired){this.Invoke(new Action(() =>{MessageBox.Show(msg, caption, MessageBoxButtons.OK, MessageBoxIcon.Error);this.Focus();}));}else{MessageBox.Show(msg, caption, MessageBoxButtons.OK, MessageBoxIcon.Error);this.Focus();}}public void ShowInformationList(List<string> msgList, string caption){if (this.InvokeRequired){this.Invoke(new Action(() =>{Frm_TipMsg frmTip = new Frm_TipMsg(caption,msgList);frmTip.ShowDialog();this.Focus();}));}else{Frm_TipMsg frmTip = new Frm_TipMsg(caption, msgList);frmTip.ShowDialog();this.Focus();}}#endregion}
基础View
/// /// Presenter业务处理的基类/// public abstract class BasePresenter where T : IBaseView{#region 静态private static DateTime timeout;/// /// 缓存数据超时时间默认一小时/// protected DateTime Timeout{get{if (BasePresenter.timeout == null){BasePresenter.timeout = new DateTime(0, 0, 0, 1, 0, 0);}return BasePresenter.timeout;}private set { BasePresenter.timeout = value; }}#endregionpublic T View{get;private set;}protected BasePresenter(T t){this.View = t;OnViewSet();}/*赋值完成之后调用调用虚方法OnViewSet。具体的Presenter可以重写该方法进行对View进行事件注册工作。但是需要注意的是,Presenter的创建是在ViewBase的构造函数中通过调用CreatePresenter方法实现,所以执行OnViewSet的时候,View本身还没有完全初始化,所以在此不能对View的控件进行操作。*/protected virtual void OnViewSet(){}}
基础Presenter

 /// /// 基本窗体接口定义/// public interface IBaseView{/// /// 窗体加载事件/// event EventHandler ViewLoad;/// /// 窗体关闭前事件/// event CancelEventHandler ViewClosing;/// /// 窗体关闭后事件/// event EventHandler ViewClosed;/// /// 弹出提示信息/// void ShowInformationMsg(string msg, string caption);/// /// 弹出错误信息/// void ShowErrorMsg(string msg, string caption);void ShowInformationList(List<string> msgList, string caption);}
基础接口
/// /// 医嘱停止的逻辑处理类/// 目的:///     1.处理医嘱停止相关的客户端逻辑。/// 使用规范:////// public class OrderStopPresenter : BasePresenter{public OrderStopPresenter(IOrderStopView t): base(t){}/// /// 医嘱查询服务实体类/// IOrderBusiness orderBussiness = new OrderBusiness();/// /// 重写基类的事件,用于在子类中注册IOrderStopView相关的事件/// protected override void OnViewSet(){base.OnViewSet(); View.OrderStoping += View_OrderStoping;View.ViewLoad += View_ViewLoad;View.QueryStopCauseInfo += View_QueryStopCauseInfo;}/// /// 查询停止原因信息/// /// void View_QueryStopCauseInfo(string obj){try{ReturnResult result = orderBussiness.SearchStopCauseInfo(obj);if (!result.Result){View.ShowErrorMsg(result.Message, ConstString.TITLE_SYSTEM_TIPS);}else{View.BindingStopCause(result.Addition as List);}}catch (Exception ee){View.ShowErrorMsg(ee.Message, ConstString.TITLE_SYSTEM_TIPS);}}#region 处理事件逻辑/// /// 医嘱停止事件/// /// /// void View_OrderStoping(object sender, OrderStopEventArgs e){try{ReturnResult result = null;if (e.IsAllStop){result = orderBussiness.StopALLOrder(e.SerialNumber, e.EndDoctorId, e.StopCaseID, e.EndNursId);}else{result = orderBussiness.StopOrder(e.Orders.Select(o=>o.Id).ToList(), e.EndDoctorId, e.StopCaseID, e.EndNursId);}if (!result.Result){View.ShowErrorMsg("停止失败!"+result.Message,ConstString.TITLE_SYSTEM_TIPS);}}catch (Exception ee){View.ShowErrorMsg(ee.Message, ConstString.TITLE_SYSTEM_TIPS);}}/// /// 窗体加载事件/// /// /// void View_ViewLoad(object sender, EventArgs e){} #endregion}
业务Presenter
/// /// 医嘱停止UI界面/// /// [CoordinatorAttribute("Fits.PatiInWorkStation.Presenter", "Fits.PatiInWorkStation.Presenter.OrderStopPresenter")]public partial class Frm_OrderStop : BaseView,IOrderStopView{#region 属性/// /// 医嘱停止原因类型字典/// private const string STOP_CAUSE_ID = "000237";/// /// 停止医师编号/// private string doctorCode { set; get; }/// /// 停止医师名称/// private string doctorName { set; get; }/// /// 标记是否全停/// private bool IsAllStop; /// /// 需要停止的医嘱编号(如果是全部停止,则传递流水号,否则传医嘱编号)/// private List orders { set; get; }/// /// 流水号(如果是全部停止,则传递流水号,否则传医嘱编号)/// private string serialNumber { set; get; }#endregion#region IOrderStopView实现/// /// 查询停止原因信息/// public event Action<string> QueryStopCauseInfo;public event EventHandler OrderStoping; public event EventHandler ViewLoad;public event CancelEventHandler ViewClosing;public event EventHandler ViewClosed;/// /// 绑定停止原因下拉框/// /// 停止原因字典public void BindingStopCause(List stopCauesList){ DataTable dataSource = new DataTable();dataSource.Columns.Add("Code");dataSource.Columns.Add("Name");cmbStopReasion.DisplayMember = "Name";cmbStopReasion.ValueMember = "Code";if (stopCauesList == null || stopCauesList.Count == 0){cmbStopReasion.DataSource = dataSource;return;}stopCauesList = stopCauesList.OrderBy(i=>i.CODEID).ToList();foreach (var item in stopCauesList){DataRow row = dataSource.NewRow();row["Code"] = item.CODEID;row["Name"] = item.CODEID+" "+item.CODENAME;dataSource.Rows.Add(row);} cmbStopReasion.DataSource = dataSource; }#endregion#region 窗体相关事件/// /// 窗体构造方法/// public Frm_OrderStop(){InitializeComponent();}/// /// 窗体构造方法/// /// /// public Frm_OrderStop(string doctorCode, string doctorName, bool isAllStop, List orders, string serialNumber): this(){this.doctorCode = doctorCode;this.doctorName = doctorName;IsAllStop = isAllStop;this.orders =orders;this.serialNumber = serialNumber;} /// /// 窗体加载事件/// /// /// private void Frm_OrderStop_Load(object sender, EventArgs e){InitUI();}private void tsbtn_StopOrder_Click(object sender, EventArgs e){if (CheckUIData()){if (IsAllStop){//var result = MessageBox.Show("是否确认全停医嘱?", ConstString.TITLE_SYSTEM_TIPS, MessageBoxButtons.YesNo, MessageBoxIcon.Question);//if (result == DialogResult.No)//{//    return;//}
                }var args=new OrderStopEventArgs(){EndDoctorId = this.doctorCode,EndDoctorName = this.doctorName,  SerialNumber=this.serialNumber,Orders = this.orders,IsAllStop=this.IsAllStop,StopCaseID=cmbStopReasion.SelectedValue as string  } ; //向Presenter发送处理 业务的请求。if (OrderStoping!=null){OrderStoping(sender, args);}this.Close();} }/// /// 退出按钮事件/// /// /// private void tsbtnExist_Click(object sender, EventArgs e){this.Close();}#endregion#region 辅助/// /// 检查界面必填项逻辑/// /// private bool CheckUIData(){if (string.IsNullOrWhiteSpace(tbStopDoctor.Text)){ShowInformationMsg("停止医生不能为空,请填写!", ConstString.TITLE_SYSTEM_TIPS);return false;}else if (cmbStopReasion.SelectedIndex == -1){ShowInformationMsg("停止原因不能为空,请填写!", ConstString.TITLE_SYSTEM_TIPS);return false;}return true;}/// /// 初始化界面信息/// private void InitUI(){lblTip.Visible = this.IsAllStop;tbStopDoctor.Text = this.doctorName;//向Presenter发送查询数据的请求。if (QueryStopCauseInfo != null){QueryStopCauseInfo(STOP_CAUSE_ID);}cmbStopReasion.Enabled = !IsAllStop; } #endregion }
业务窗体
/// ///停止医嘱的客户端的接口/// 目的:///     1.规范客户段的操作,同时将操作对外公布,便于Presenter与view交互。///使用规范:////// public interface IOrderStopView : IBaseView{/// /// 医嘱停止事件/// event EventHandler OrderStoping;/// /// 查询停止原因信息/// event Action<string> QueryStopCauseInfo;/// /// 绑定停止原因下拉框/// /// 停止原因字典void BindingStopCause(List stopCauesList);}
业务接口

由上面的代码可以看出,Presenter通过依赖View的接口(在构造函数中,接收View的实体 t),订阅了View接口中的所有事件。窗体中用户提交请求时,只需要简单判断事件不为空,之后就可以通过调用事件的方式,提交请求到Presenter。而界面上呈现数据的逻辑,View自己实现了呈现逻辑,然后通过接口公布给Presenter,Presenter在需要呈现数据时进行调用。在此过程中,View是不知道何时可以呈现数据,一切由Presenter控制。View告诉Presenter用户的请求,接下来的事就全交给Presenter。

总结:

 由此,最大限度的将业务逻辑抽离到Presenter中处理,可以将View的开发完全独立,只需要先将所有请求,规范成接口事件,客户端逻辑自己实现,其他逻辑通过事件与Presenter交互。 

模型与视图完全分离,我们可以修改视图而不影响模型;可以更高效地使用模型,因为所有的交互都发生在一个地方——Presenter内部; 

如果我们把逻辑放在Presenter中,那么我们就可以脱离用户接口来测试这些逻辑(单元测试)

  

 

  

转载于:https://www.cnblogs.com/YangFengHui/p/4601260.html

更多相关:

  • 关于Graphics也有了基本了解下面想说的的是学这个东东干什么呢,到底如何应用目前常见应用1、验证码(参照网上的)2、打印排版(会提到关于条形码大小设置)3、自定义控件 一、验证码 1 class ValidateCode 2 { 3 #region 定义和初始化配置字段 4...

  • 关于具体需求,请看前面的博文:DDD领域驱动设计实践篇之如何提取模型,下面是具体的实体、聚合、值对象的代码,不想多说什么是实体、聚合等概念,相信理论的东西大家已经知晓了。本人对DDD表示好奇,没有在真正项目实践过,甚至也没有看过真正的DDD实践的项目源码,处于极度纠结状态,甚至无法自拔,所以告诫DDD爱好者们,如果要在项目里面实践DD...

  • TPL Dataflow是微软面向高并发应用而推出的新程序库。借助于异步消息传递与管道,它可以提供比线程池更好的控制。本身TPL库在DataflowBlock类中提供了不少扩展函数,用起来还是非常方便的,但感觉还是不够全(当然,MS没必要设计大而全的接口),前段时间写个小程序的时候用到了它,当时顺便写了几个扩展函数,这里记录一下,如果...

  • 前言       写系列文章的时候[前言]部分变得无言了,可能来得顺利了点吧: ) 本章中提供的封装均是我用笨办法从<>和<>中拷贝出来并参照VC++代码进行整理的,主要是针对HikServ...

  • app.js App({onLaunch: function() {if (!wx.cloud) {console.error('请使用 2.2.3 或以上的基础库以使用云能力')} else {wx.cloud.init({// env 参数说明:// env 参数决定接下来小程序发起的云开发调用(wx.cloud.xxx...

  • MVC、MVP、MVVM这些模式是为了解决开发过程中的实际问题而提出来的,目前作为主流的几种架构模式而被广泛使用。 一、MVC(Model-View-Controller) MVC是比较直观的架构模式,用户操作->View(负责接收用户的输入操作)->Controller(业务逻辑处理)->Model(数据持久化)->View(将结...

  • *自适应向布局约束的转化关闭*/ #define PREPCONSTRAINTS(VIEW) [VIEW setTranslatesAutoresizingMaskIntoConstraints:NO] #define CONSTRAIN(PARENT, VIEW, FORMAT) [PARENT addConstraints:[N...