浅谈MVP设计模式
最近公司在做一個醫(yī)療項目,使用WinForm界面作為客戶端交互界面。在整個客戶端解決方案中。使用了MVP模式實現(xiàn)。由于之前沒有接觸過該設(shè)計模式,所以在項目完成到某個階段時,將使用MVP的體會寫在博客里面。
所謂的MVP指的是Model,View,Presenter。對于一個UI模塊來說,它的所有功能被分割為三個部分,分別通過Model、View和Presenter來承載。Model、View和Presenter相互協(xié)作,完成對最初數(shù)據(jù)的呈現(xiàn)和對用戶操作的響應(yīng),它們具有各自的職責(zé)劃分。Model可以看成是模塊的業(yè)務(wù)邏輯和數(shù)據(jù)的提供者;View專門負(fù)責(zé)數(shù)據(jù)可視化的呈現(xiàn),和用戶交互事件的響應(yīng)。一般地,View會實現(xiàn)一個相應(yīng)的接口;Presenter是一般充當(dāng)Model和View的紐帶。
其依賴關(guān)系為:
View直接依賴Presenter,即在View實體保存了Presenter的引用;?而Presenter通過依賴View的接口,實現(xiàn)對View的數(shù)據(jù)推送和數(shù)據(jù)呈現(xiàn)任務(wù)的分配(Presenter處理完業(yè)務(wù)邏輯之后,在需要展示數(shù)據(jù)時,通過調(diào)用View的接口,將數(shù)據(jù)推送給View);Model與View之間不存在依賴關(guān)系,Model與Presenter之間存在依賴關(guān)系。
如下圖所示:
MVP模式中有一個比較特殊的地方,就是雖然View有依賴Preserter,但是不應(yīng)該由View主動的去訪問Presenter,View職責(zé):接收用戶的的請求,將請求提交給Presenter,在適合的時候展示數(shù)據(jù)(Presenter處理完請求之后,會主動將數(shù)據(jù)推送)。如何設(shè)計,才能防止View主動訪問Presenter呢?通過事件訂閱的機制,View的接口中,將所有可能處理的請求做成事件,然后由Presenter去訂閱該事件,那么客戶端需要處理請求時,就直接調(diào)用事件。代碼如下:
/// <summary>/// 窗體的抽象基類/// </summary>public partial class BaseView : DockContent{protected ViewHelper viewHelper;public BaseView(){InitializeComponent();//viewHelper負(fù)責(zé)動態(tài)的創(chuàng)建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}基礎(chǔ)View
/// <summary>/// Presenter業(yè)務(wù)處理的基類/// </summary>public abstract class BasePresenter<T> where T : IBaseView{#region 靜態(tài)private static DateTime timeout;/// <summary>/// 緩存數(shù)據(jù)超時時間默認(rèn)一小時/// </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();}/*賦值完成之后調(diào)用調(diào)用虛方法OnViewSet。具體的Presenter可以重寫該方法進行對View進行事件注冊工作。但是需要注意的是,Presenter的創(chuàng)建是在ViewBase的構(gòu)造函數(shù)中通過調(diào)用CreatePresenter方法實現(xiàn),所以執(zhí)行OnViewSet的時候,View本身還沒有完全初始化,所以在此不能對View的控件進行操作。*/protected virtual void OnViewSet(){}}基礎(chǔ)Presenter
/// <summary>/// 基本窗體接口定義/// </summary>public interface IBaseView{/// <summary>/// 窗體加載事件/// </summary>event EventHandler ViewLoad;/// <summary>/// 窗體關(guān)閉前事件/// </summary>event CancelEventHandler ViewClosing;/// <summary>/// 窗體關(guān)閉后事件/// </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);}基礎(chǔ)接口
/// <summary>/// 醫(yī)囑停止的邏輯處理類/// 目的:/// 1.處理醫(yī)囑停止相關(guān)的客戶端邏輯。/// 使用規(guī)范:/// 略/// </summary>public class OrderStopPresenter : BasePresenter<IOrderStopView>{public OrderStopPresenter(IOrderStopView t): base(t){}/// <summary>/// 醫(yī)囑查詢服務(wù)實體類/// </summary>IOrderBusiness orderBussiness = new OrderBusiness();/// <summary>/// 重寫基類的事件,用于在子類中注冊IOrderStopView相關(guān)的事件/// </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>/// 醫(yī)囑停止事件/// </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}業(yè)務(wù)Presenter
/// <summary>/// 醫(yī)囑停止UI界面/// /// </summary>[CoordinatorAttribute("Fits.PatiInWorkStation.Presenter", "Fits.PatiInWorkStation.Presenter.OrderStopPresenter")]public partial class Frm_OrderStop : BaseView,IOrderStopView{#region 屬性/// <summary>/// 醫(yī)囑停止原因類型字典/// </summary>private const string STOP_CAUSE_ID = "000237";/// <summary>/// 停止醫(yī)師編號/// </summary>private string doctorCode { set; get; }/// <summary>/// 停止醫(yī)師名稱/// </summary>private string doctorName { set; get; }/// <summary>/// 標(biāo)記是否全停/// </summary>private bool IsAllStop; /// <summary>/// 需要停止的醫(yī)囑編號(如果是全部停止,則傳遞流水號,否則傳醫(yī)囑編號)/// </summary>private List<View_IdNameInfo> orders { set; get; }/// <summary>/// 流水號(如果是全部停止,則傳遞流水號,否則傳醫(yī)囑編號)/// </summary>private string serialNumber { set; get; }#endregion#region IOrderStopView實現(xiàn)/// <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 窗體相關(guān)事件/// <summary>/// 窗體構(gòu)造方法/// </summary>public Frm_OrderStop(){InitializeComponent();}/// <summary>/// 窗體構(gòu)造方法/// </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("是否確認(rèn)全停醫(yī)囑?", 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發(fā)送處理 業(yè)務(wù)的請求。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("停止醫(yī)生不能為空,請?zhí)顚?#xff01;", ConstString.TITLE_SYSTEM_TIPS);return false;}else if (cmbStopReasion.SelectedIndex == -1){ShowInformationMsg("停止原因不能為空,請?zhí)顚?#xff01;", ConstString.TITLE_SYSTEM_TIPS);return false;}return true;}/// <summary>/// 初始化界面信息/// </summary>private void InitUI(){lblTip.Visible = this.IsAllStop;tbStopDoctor.Text = this.doctorName;//向Presenter發(fā)送查詢數(shù)據(jù)的請求。if (QueryStopCauseInfo != null){QueryStopCauseInfo(STOP_CAUSE_ID);}cmbStopReasion.Enabled = !IsAllStop; } #endregion }業(yè)務(wù)窗體
/// <summary>///停止醫(yī)囑的客戶端的接口/// 目的:/// 1.規(guī)范客戶段的操作,同時將操作對外公布,便于Presenter與view交互。///使用規(guī)范:/// 略/// </summary>public interface IOrderStopView : IBaseView{/// <summary>/// 醫(yī)囑停止事件/// </summary>event EventHandler<OrderStopEventArgs> OrderStoping;/// <summary>/// 查詢停止原因信息/// </summary>event Action<string> QueryStopCauseInfo;/// <summary>/// 綁定停止原因下拉框/// </summary>/// <param name="stopCauesList">停止原因字典</param>void BindingStopCause(List<DICT_CODE> stopCauesList);}業(yè)務(wù)接口
由上面的代碼可以看出,Presenter通過依賴View的接口(在構(gòu)造函數(shù)中,接收View的實體 t),訂閱了View接口中的所有事件。窗體中用戶提交請求時,只需要簡單判斷事件不為空,之后就可以通過調(diào)用事件的方式,提交請求到Presenter。而界面上呈現(xiàn)數(shù)據(jù)的邏輯,View自己實現(xiàn)了呈現(xiàn)邏輯,然后通過接口公布給Presenter,Presenter在需要呈現(xiàn)數(shù)據(jù)時進行調(diào)用。在此過程中,View是不知道何時可以呈現(xiàn)數(shù)據(jù),一切由Presenter控制。View告訴Presenter用戶的請求,接下來的事就全交給Presenter。
總結(jié):
?由此,最大限度的將業(yè)務(wù)邏輯抽離到Presenter中處理,可以將View的開發(fā)完全獨立,只需要先將所有請求,規(guī)范成接口事件,客戶端邏輯自己實現(xiàn),其他邏輯通過事件與Presenter交互。?
模型與視圖完全分離,我們可以修改視圖而不影響模型;可以更高效地使用模型,因為所有的交互都發(fā)生在一個地方——Presenter內(nèi)部;?
如果我們把邏輯放在Presenter中,那么我們就可以脫離用戶接口來測試這些邏輯(單元測試)??
?
轉(zhuǎn)載于:https://www.cnblogs.com/YangFengHui/p/4601260.html
總結(jié)
- 上一篇: 宝骏车多少钱啊?
- 下一篇: Postmortem报告