最近公司在作一个医疗项目,使用WinForm界面做为客户端交互界面。在整个客户端解决方案中。使用了MVP模式实现。因为以前没有接触过该设计模式,因此在项目完成到某个阶段时,将使用MVP的体会写在博客里面。设计模式
所谓的MVP指的是Model,View,Presenter。对于一个UI模块来讲,它的全部功能被分割为三个部分,分别经过Model、View和Presenter来承载。Model、View和Presenter相互协做,完成对最初数据的呈现和对用户操做的响应,它们具备各自的职责划分。Model能够当作是模块的业务逻辑和数据的提供者;View专门负责数据可视化的呈现,和用户交互事件的响应。通常地,View会实现一个相应的接口;Presenter是通常充当Model和View的纽带。缓存
其依赖关系为:ide
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去订阅该事件,那么客户端须要处理请求时,就直接调用事件。代码以下:测试
/// <summary> /// 窗体的抽象基类 /// </summary> 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 }
/// <summary> /// Presenter业务处理的基类 /// </summary> public abstract class BasePresenter<T> where T : IBaseView { #region 静态 private static DateTime timeout; /// <summary> /// 缓存数据超时时间默认一小时 /// </summary> protected DateTime Timeout { get { if (BasePresenter<T>.timeout == null) { BasePresenter<T>.timeout = new DateTime(0, 0, 0, 1, 0, 0); } return BasePresenter<T>.timeout; } private set { BasePresenter<T>.timeout = value; } } #endregion public 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() { } }
/// <summary> /// 基本窗体接口定义 /// </summary> public interface IBaseView { /// <summary> /// 窗体加载事件 /// </summary> event EventHandler ViewLoad; /// <summary> /// 窗体关闭前事件 /// </summary> event CancelEventHandler ViewClosing; /// <summary> /// 窗体关闭后事件 /// </summary> event EventHandler ViewClosed; /// <summary> /// 弹出提示信息 /// </summary> void ShowInformationMsg(string msg, string caption); /// <summary> /// 弹出错误信息 /// </summary> void ShowErrorMsg(string msg, string caption); void ShowInformationList(List<string> msgList, string caption); }
/// <summary> /// 医嘱中止的逻辑处理类 /// 目的: /// 1.处理医嘱中止相关的客户端逻辑。 /// 使用规范: /// 略 /// </summary> public class OrderStopPresenter : BasePresenter<IOrderStopView> { public OrderStopPresenter(IOrderStopView t) : base(t) { } /// <summary> /// 医嘱查询服务实体类 /// </summary> IOrderBusiness orderBussiness = new OrderBusiness(); /// <summary> /// 重写基类的事件,用于在子类中注册IOrderStopView相关的事件 /// </summary> protected override void OnViewSet() { base.OnViewSet(); View.OrderStoping += View_OrderStoping; View.ViewLoad += View_ViewLoad; View.QueryStopCauseInfo += View_QueryStopCauseInfo; } /// <summary> /// 查询中止缘由信息 /// </summary> /// <param name="obj"></param> 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<DICT_CODE>); } } catch (Exception ee) { View.ShowErrorMsg(ee.Message, ConstString.TITLE_SYSTEM_TIPS); } } #region 处理事件逻辑 /// <summary> /// 医嘱中止事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> 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); } } /// <summary> /// 窗体加载事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> void View_ViewLoad(object sender, EventArgs e) { } #endregion }
/// <summary> /// 医嘱中止UI界面 /// /// </summary> [CoordinatorAttribute("Fits.PatiInWorkStation.Presenter", "Fits.PatiInWorkStation.Presenter.OrderStopPresenter")] public partial class Frm_OrderStop : BaseView,IOrderStopView { #region 属性 /// <summary> /// 医嘱中止缘由类型字典 /// </summary> private const string STOP_CAUSE_ID = "000237"; /// <summary> /// 中止医师编号 /// </summary> private string doctorCode { set; get; } /// <summary> /// 中止医师名称 /// </summary> private string doctorName { set; get; } /// <summary> /// 标记是否全停 /// </summary> private bool IsAllStop; /// <summary> /// 须要中止的医嘱编号(若是是所有中止,则传递流水号,不然传医嘱编号) /// </summary> private List<View_IdNameInfo> orders { set; get; } /// <summary> /// 流水号(若是是所有中止,则传递流水号,不然传医嘱编号) /// </summary> private string serialNumber { set; get; } #endregion #region IOrderStopView实现 /// <summary> /// 查询中止缘由信息 /// </summary> public event Action<string> QueryStopCauseInfo; public event EventHandler<OrderStopEventArgs> OrderStoping; public event EventHandler ViewLoad; public event CancelEventHandler ViewClosing; public event EventHandler ViewClosed; /// <summary> /// 绑定中止缘由下拉框 /// </summary> /// <param name="stopCauesList">中止缘由字典</param> public void BindingStopCause(List<DICT_CODE> 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 窗体相关事件 /// <summary> /// 窗体构造方法 /// </summary> public Frm_OrderStop() { InitializeComponent(); } /// <summary> /// 窗体构造方法 /// </summary> /// <param name="doctorCode"></param> /// <param name="doctorName"></param> public Frm_OrderStop(string doctorCode, string doctorName, bool isAllStop, List<View_IdNameInfo> orders, string serialNumber) : this() { this.doctorCode = doctorCode; this.doctorName = doctorName; IsAllStop = isAllStop; this.orders =orders; this.serialNumber = serialNumber; } /// <summary> /// 窗体加载事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> 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(); } } /// <summary> /// 退出按钮事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void tsbtnExist_Click(object sender, EventArgs e) { this.Close(); } #endregion #region 辅助 /// <summary> /// 检查界面必填项逻辑 /// </summary> /// <returns></returns> 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; } /// <summary> /// 初始化界面信息 /// </summary> private void InitUI() { lblTip.Visible = this.IsAllStop; tbStopDoctor.Text = this.doctorName; //向Presenter发送查询数据的请求。 if (QueryStopCauseInfo != null) { QueryStopCauseInfo(STOP_CAUSE_ID); } cmbStopReasion.Enabled = !IsAllStop; } #endregion }
/// <summary> ///中止医嘱的客户端的接口 /// 目的: /// 1.规范客户段的操做,同时将操做对外公布,便于Presenter与view交互。 ///使用规范: /// 略 /// </summary> public interface IOrderStopView : IBaseView { /// <summary> /// 医嘱中止事件 /// </summary> event EventHandler<OrderStopEventArgs> OrderStoping; /// <summary> /// 查询中止缘由信息 /// </summary> event Action<string> QueryStopCauseInfo; /// <summary> /// 绑定中止缘由下拉框 /// </summary> /// <param name="stopCauesList">中止缘由字典</param> void BindingStopCause(List<DICT_CODE> stopCauesList); }
由上面的代码能够看出,Presenter经过依赖View的接口(在构造函数中,接收View的实体 t),订阅了View接口中的全部事件。窗体中用户提交请求时,只须要简单判断事件不为空,以后就能够经过调用事件的方式,提交请求到Presenter。而界面上呈现数据的逻辑,View本身实现了呈现逻辑,而后经过接口公布给Presenter,Presenter在须要呈现数据时进行调用。在此过程当中,View是不知道什么时候能够呈现数据,一切由Presenter控制。View告诉Presenter用户的请求,接下来的事就全交给Presenter。ui
总结:this
由此,最大限度的将业务逻辑抽离到Presenter中处理,能够将View的开发彻底独立,只须要先将全部请求,规范成接口事件,客户端逻辑本身实现,其余逻辑经过事件与Presenter交互。 spa
模型与视图彻底分离,咱们能够修改视图而不影响模型;能够更高效地使用模型,由于全部的交互都发生在一个地方——Presenter内部; 设计