当前位置: 首页 > 编程日记 > 正文

浅谈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去订阅该事件,那么客户端需要处理请求时,就直接调用事件。代码如下:

/// <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}
基础View
/// <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; }}#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
 /// <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}
业务Presenter
/// <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。

总结:

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

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

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

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

相关文章:

C#委托与事件

之前写过一篇关于C#委托与事件的文章&#xff08;见《C#委托和事件例析》&#xff09;&#xff0c;不过还是收到一些网友的提问。所以&#xff0c;今天再换另一个角度来详解一下这个问题。 一、在控制台下使用委托和事件 我们都知道&#xff0c;C#中有“接口”这个概念&#xf…

Docker for mac安装

Mac安装Docker docker下载地址: https://hub.docker.com/editions/community/docker-ce-desktop-mac docker for mac document: https://docs.docker.com/docker-for-mac/ 系统要求 Docker Desktop - Mac适用于OS X Sierra 10.12和更新的macOS版本。 获得Docker 稳定边缘Stable…

白盒测试--基本路径测试法

1.为什么要有基本路径测试法&#xff1f; 对于路径测试&#xff0c;最理想的情况是路径全部覆盖&#xff0c;单对于复杂的大程序要做到路径覆盖是不可能的&#xff0c;因此可以采用基本路径测试。 2.基本路径测试法的步骤&#xff1f; &#xff08;1&#xff09;画出程序的控制…

Postmortem报告

1. 每个成员在beta 阶段的实践和alpha 阶段有何改进? 2. 团队在beta 阶段吸取了那些alpha 阶段的经验教训? 在alpha阶段中&#xff0c;虽然我们团队已经对软件主要功能核心代码完成了&#xff0c;但是由于我们团队现有掌握有关于安卓开发的技术并不成熟&#xff0c;无法对软件…

是否可以人为修改发表时间

是否可以人为修改发表时间转载于:https://blog.51cto.com/14188306/2346747

关于win7_iis报500.19和500.21错误的解决方法

关于win7_iis报500.19和500.21错误的解决方法HTTP 错误 500.19 Internal Server Error的解决方法WIN7下.Net开发遇到的又一问题&#xff1a;HTTP 错误 500.19 - Internal Server Error&#xff0c;无法访问请求的页面&#xff0c;因为该页的相关配置数据无效。详细错误信息模块…

黑盒测试--因果图法

例子&#xff1a; (1)根据题目可以得到原因和结果分别是&#xff1a; &#xff08;2&#xff09;画出因果图 根据题意来画因果图&#xff0c;输入第一个字符是A或B要写成一个状态&#xff0c;且第二个字符为数字。 画因果图主要就是理清不同状态之间的关系&#xff0c;还有有…

php 学习笔记 数组1

1、一般情况下$name[tom]和$name[tom]是相同的&#xff1b;但没有引号的键不能和常量区别开&#xff0c;如&#xff1a;define(index, 5)时&#xff1b;$name[tom]和$name[tom]不同 2、双引号里的变量一般要用{}括起来是好习惯&#xff0c;如&#xff1a; echo "{$name}&q…

Linux的文件系统

一、文件的属性 linux文件属性的格式为- --- --- ---。第一位为文件的类型&#xff0c;剩下的9位&#xff0c;每三位为一组&#xff0c;分别对应文件所有者&#xff0c;文件所以者所属的用户组&#xff0c;其他人的关系。 r为可读&#xff0c;w为可写&#xff0c;x为可执行。如…

Python-100 练习题 01 列表推导式

最近打算好好练习下 python&#xff0c;因此找到一个练习题网站&#xff0c;打算每周练习 3-5 题吧。 www.runoob.com/python/pyth… 另外&#xff0c;这个网站其实也还有 Python 的教程&#xff0c;从基础到高级的知识都有。 Example-1 三位数组合 题目&#xff1a;有四个数字…

在思科模拟器下搭建WWW、DNS、FTP、Email服务

目录一、搭建基本的拓扑结构二、配置基本IP三、配置静态路由Router0&#xff1a;Router1:四、搭建WWW服务0号服务器&#xff1a;1号服务器&#xff1a;五、在pc0上测试www服务六、搭建FTP服务对于3号服务器&#xff1a;七、在pc0上测试搭建的FTP服务八、搭建E-mail服务对于2号服…

Android SQLite数据库之事务的学习

SQLite是Android系统内置的一款轻量级的关系型数据库&#xff0c;它的运算速度非常快&#xff0c;占用资源很少&#xff0c;通常只需要几百K的内存就足够了。SQLite不仅支持标准的SQL语法&#xff0c;还遵循了数据库的ACID事务。 模拟一个应用场景&#xff1a;进行一次转账操作…

1组合逻辑电路--多路选择器与多路分解器

1.2多路选择器 1.2.1不带优先级的多路选择器 四路选择器如下 代码如下 View Code 1 module multiplexer 2 ( 3 input iA, 4 input iB, 5 input iC, 6 input iD, 7 input [1:0] iSel, 8 output reg oQ 9 );10 11 always (*)12 begin13 case(iSel)1…

java——慎用可变参数列表

说起可变参数&#xff0c;我们先看下面代码段&#xff0c;对它有个直观的认识&#xff0c;下方的红字明确地解释了可变参数的意思&#xff1a; 1 public class VarargsDemo{2 3 static int sum(int... args) {4 int sum 0;5 for(int arg:args)6 …

一位老码农的分享:一线程序员该如何面对「中年危机」?

如果这是第二次看到我的文章&#xff0c;欢迎文末扫码订阅我个人的公众号&#xff08;跨界架构师&#xff09;哟~ 本文长度为2728字&#xff0c;建议阅读8分钟。坚持原创&#xff0c;每一篇都是用心之作&#xff5e;先来聊一下这个问题的背景吧。前两天有小伙伴问到Z哥这个问题…

白话spring依赖注入

Spring能有效地组织J2EE应用各层的对象。Action&#xff1f;Service&#xff1f;DAO&#xff1f;&#xff0c;都可在Spring的管理下有机地协调、运行。Spring将各层的对象以松耦合的方式组织在一起&#xff0c;对象与对象之间没有直接的联系&#xff0c;各层对象的调用完全面向…

2软件测试初相识

软件测试初相识 软件测试初识为什么要做软件测试,做软件测试的必要性是什么?关于软件测试的定义有很多种软件测试的两面性软件测试的价值总结软件测试初相识 文章目录 软件测试初识为什么要做软件测试,做软件测试的必要性是什么?关于软件测试的定义有很多种软件测试的两面性…

【go】sdk + idea-plugin 开发工具安装

http://golang.org/doc/install/source第一步&#xff1a;windows 安装 git第二步$ git clone https://go.googlesource.com/go $ cd go $ git checkout go1.4.1保持翻墙姿势 D:\Program Files (x86)\Git\bin>git clone https://go.googlesource.com/go Cloning into go... …

无限极:对虚假宣传行为查处不力 推十条整改措施

中新网1月28日电 无限极今日发表声明称&#xff0c;将进一步响应整治“保健”市场乱象百日行动&#xff0c;承担主体责任&#xff0c;落实专项整改。 声明全文如下&#xff1a; 1月8日&#xff0c;国家市场监督管理总局等13部委联合发出《关于开展联合整治“保健”市场乱象百日…

基于WTL自绘 - 表情选择界面

1.无窗口绘制(网格&#xff0c; 背景&#xff0c;图片&#xff0c;预览)。2.采用Cximage处理图像。3.关于源码 请发邮件 satngqq.com4.已经增加到wtl_duilib 0.5版Demo中转载于:https://www.cnblogs.com/satng/archive/2011/08/13/2138780.html

3软件测试原理与软件缺陷

软件测试原理与软件缺陷 文章目录 前言软件测试原理软件缺陷总结前言 知其然,更要知其所以然。 软件测试原理 下图中很直观的展示出了软件测试原理。根据测试目标设计测试输出,依据测试依据(包括软件需求、设计等)给出预期结果,在被测对象上运行得到运行结果,将运行结果…

Oracle wrap 和 unwrap( 加密与解密) 说明

一. Wrap 说明官网的说明如下&#xff1a;A PL/SQL Source Text Wrappinghttp://download.oracle.com/docs/cd/E11882_01/appdev.112/e17126/wrap.htm#LNPLS1744You can wrap the PL/SQL source text for any of these stored PL/SQL units, thereby preventing anyone from di…

python的沙盒环境virtualenv(二)--简单使用

安装 sudo apt-get install python-virtualenv使用方法 virtualenv [虚拟环境名称] 如&#xff0c;创建**ENV**的虚拟环境 virtualenv ENV默认情况下&#xff0c;虚拟环境会依赖系统环境中的site packages&#xff0c;就是说系统中已经安装好的第三方package也会安装在虚拟环境…

使用Powershell批量为Azure资源添加Tags

在使用Azure的过程中&#xff0c;我们可以将Tags应用于Azure资源&#xff0c;从而可以将元数据逻辑的组织到某些分类中。每个Tags由名称和值组成。例如&#xff0c;我们可以将名称“Environment”和值“Production”应用于生产中的所有资源。应用Tags以后&#xff0c;我们可以使…

4软件测试阶段

软件测试流程和规范前言软件测试阶段总结前言 不以规矩&#xff0c;不成方圆。 软件测试阶段 在软件测试中主要的测试阶段包括文档集测试、单元测试、集成测试、系统测试&#xff08;包括功能测试和非功能测试&#xff09;、安装测试、验收测试。 总结 不同的测试阶段有不同…

InfluxDB 2.0 Alpha展开测试!将会加入查询语言Flux

InfluxData释出其开源时序数据库InfluxDB 2.0 Alpha测试版&#xff0c;这个版本最大的更新&#xff0c;便是增加了新的数据脚本和查询语言Flux&#xff0c;不只能提供跨平台时序数据操作&#xff0c;还能将TICK组件堆栈整合成一个更加一致的平台。InfluxDB是一个以Go语言开发的…

Weka 开发[1]-Instances类

先google一下&#xff0c;把Weka软件下载下来&#xff0c;安装完成之后&#xff0c;在Weka的安装目录中有一个weka.jar的包。 把包添加到工程中后&#xff0c;就可以调用weka中的函数了。 再介绍一点weka的基本知识&#xff0c;在weka的目录下&#xff0c;有一个data的文件夹&a…

SQL Server 2005 18452登录错误 的解决方法

无法连接到服务器 服务器&#xff1a;消息18452&#xff0c; 级别16&#xff0c;状态1 [Microsoft][ODBC SQL Server Driver][SQL Server]用户‘sa’登陆失败。原因&#xff1a;未与信任SQL Server连接相关联 该错误发生的原因是由于SQL Ser…

5软件开发与软件测试

软件开发与软件测试 前言瀑布模型V模型总结前言 软件测试与软件开发过程是相辅相成、相互依赖、相互转换的过程。 瀑布模型 瀑布模型规定了软件生命周期中的各项活动,包括需求分析、概要设计、编码、测试、验收与交付、使用与维护等。瀑布模型中各个软件过程是自上而下、相互…

认识docker

一、Docker工作原理 二、Docker容器和虚拟机对比 三、镜像容器管理 1、Docker关键组件 2、Docker架构 3、Docker内部组件 镜像&#xff08;Image&#xff09;——一个特殊的文件系统 Docker 镜像是一个特殊的文件系统&#xff0c;除了提供容器运行时所需的程序、库、资源、配置…