浅谈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
總結
- 上一篇: 宝骏车多少钱啊?
- 下一篇: Postmortem报告