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

基础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 = "";
/// <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 == )
{
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 == -)
{
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中,那么我们就可以脱离用户接口来测试这些逻辑(单元测试)

  

浅谈MVP设计模式的更多相关文章

  1. 浅谈 MVP in Android(转)

    我自己写的demo:https://pan.baidu.com/s/1dFImVYD 一.概述 对于MVP(Model View Presenter),大多数人都能说出一二:“MVC的演化版本”,“让 ...

  2. 浅谈 MVVM 设计模式在 Unity3D 中的设计与实施

    初识 MVVM 谈起 MVVM 设计模式,可能第一映像你会想到 WPF/Sliverlight,他们提供了的数据绑定(Data Binding),命令(Command)等功能,这让 MVVM 模式得到 ...

  3. 浅谈MVP架构及开发模式

    Model-View-Presenter(MVP)概述    MVC模式已经出现了几十年了,在GUI领域已经得到了广泛的应用,由于微软ASP.NET MVC Framework的出现,致使MVC一度成 ...

  4. 浅谈JAVA设计模式

    没有万行的代码量,就不要想搞清楚设计模式.目前本人代码量大约在六千五百行,2016年需要继续努力,尽快完成万行之约. 工作之余需要,下面提前简单讨论一下设计模式. 创建型模式,共五种:工厂模式.抽象工 ...

  5. 浅谈C++设计模式之工厂方法(Factory Method)

    为什么要用设计模式?根本原因是为了代码复用,增加可维护性. 面向对象设计坚持的原则:开闭原则(Open Closed Principle,OCP).里氏代换原则(Liskov Substitution ...

  6. 浅谈 MVP in Android

    一.概述 对于MVP(Model View Presenter),大多数人都能说出一二:“MVC的演化版本”,“让Model和View完全解耦”等等.本篇博文仅是为了做下记录,提出一些自己的看法,和帮 ...

  7. 【Java基础】浅谈常见设计模式

    Num1:单例模式 基本概念:保证一个类仅有一个实例,并提供一个访问它的全局访问点. 常见写法: 懒汉式 public class Singleton { /* 持有私有静态实例,防止被引用,此处赋值 ...

  8. 浅谈js设计模式 — 命令模式

    命令模式最常见的应用场景是:有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么.此时希望用一种松耦合的方式来设计程序,使得请求发送者和请求接收者能够消除彼此之间的耦 ...

  9. 浅谈js设计模式之代理模式

    代理模式是一种非常有意义的模式,在生活中可以找到很多代理模式的场景.比如,明星都有经纪人作为代理.如果想请明星来办一场商业演出,只能联系他的经纪人.经纪人会把商业演出的细节和报酬都谈好之后,再把合同交 ...

随机推荐

  1. vue 正则判断

    value=value.replace(/[^\d.]/g,'').replace(/\.{2,}/g,'.').replace('.','$#$').replace(/\./g,'').replac ...

  2. k8s基于canel的网络策略

    Kubernetes能够把集群中不同Node节点上的Pod连接起来,并且默认情况下,每个Pod之间是可以相互访问的.但在某些场景中,不同的Pod不应该互通,这个时候就需要进行访问控制.亲测:在kube ...

  3. JAVA基础篇—异常

    五种常见异常 1.NullPointerException 空指针 2.ClassNotFoundException 指定类不存在 3.ArithmeticException运算异常 4.ArrayI ...

  4. UVa - 12096 集合栈计算机(STL)

    [题意] 有一个专门为了集合运算而设计的“集合栈”计算机.该机器有一个初始为空的栈,并且支持以下操作:PUSH:空集“{}”入栈DUP:把当前栈顶元素复制一份后再入栈UNION:出栈两个集合,然后把两 ...

  5. POJ:2342-Anniversary party(树形dp入门题目)

    传送门:http://poj.org/problem?id=2342 Anniversary party Time Limit: 1000MS Memory Limit: 65536K Descrip ...

  6. hiho 1050 树的直径

    #1050 : 树中的最长路 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 上回说到,小Ho得到了一棵二叉树玩具,这个玩具是由小球和木棍连接起来的,而在拆拼它的过程中, ...

  7. 安装lwqq

    $ git clone https://github.com/xiehuc/pidgin-lwqq.git $ cd pidgin-lwqq/ $ sudo apt-get install cmake ...

  8. 2015四川省赛 D Vertex Cover 搜索

    题意: 给出一个\(n\)个点\(m\)条边的无向图,现在要给若干个点染色,使得每条边都至少邻接一个被染色的顶点.问至少要给多少各点染色才能满足条件. 分析: 注意到题目中有一个很特殊的条件: 对于图 ...

  9. 20,序列化模块 json,pickle,shelve

    序列化模块 什么叫序列化? 将原本的字典,列表等内容转换成一个字符串的过程叫做序列化. 序列化的目的? 数据结构 通过序列化 转成 str. str 通过反序列化 转化成数据结构. json: jso ...

  10. 【15】ES6 for Humans: The Latest Standard of JavaScript: ES2015 and Beyond

    [15]ES6 for Humans 共148页: 目前看到:已经全部阅读.   亚马逊地址: 魔芋:总结: 我先看的是阮一峰的在线书籍.这本书的内容很多都与之重复的. 居然卖¥463.也是没谁了. ...