博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
浅谈MVP设计模式
阅读量:4612 次
发布时间:2019-06-09

本文共 13811 字,大约阅读时间需要 46 分钟。

  最近公司在做一个医疗项目,使用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
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; } } #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() { } }
基础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
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
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
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

你可能感兴趣的文章
【洛谷1829】 [国家集训队] Crash的数字表格(重拾莫比乌斯反演)
查看>>
[转]免费api大全
查看>>
git 认证问题之一的解决 : http ssh 互换
查看>>
sql where 1=1作用
查看>>
搜索算法----二分查找
查看>>
Python语言编程
查看>>
[poj 1469]Courses
查看>>
Xcode8出现AQDefaultDevice(173):Skipping input stram 0 0 0x0
查看>>
数据结构(二十四)二叉树的链式存储结构(二叉链表)
查看>>
Material Design Lite,简洁惊艳的前端工具箱 之 布局组件。
查看>>
关于bootstrap Modal弹窗 滚动条的问题
查看>>
Django----------路由控制
查看>>
将数字转化为字符串的快捷方式
查看>>
java23种设计模式
查看>>
冲刺周期一--站立会议04
查看>>
支持IE6以上阴影效果纯CSS
查看>>
优化算法与特征缩放
查看>>
NOIP模板复习(4)区间操作之莫队算法,树状数组,线段树
查看>>
深入理解PHP中的引用和赋值
查看>>
Shell父进程获取子进程的变量值
查看>>