最近公司在作一個醫療項目,使用WinForm界面做爲客戶端交互界面。在整個客戶端解決方案中。使用了MVP模式實現。因爲以前沒有接觸過該設計模式,因此在項目完成到某個階段時,將使用MVP的體會寫在博客裏面。設計模式
所謂的MVP指的是Model,View,Presenter。對於一個UI模塊來講,它的全部功能被分割爲三個部分,分別經過Model、View和Presenter來承載。Model、View和Presenter相互協做,完成對最初數據的呈現和對用戶操做的響應,它們具備各自的職責劃分。Model能夠當作是模塊的業務邏輯和數據的提供者;View專門負責數據可視化的呈現,和用戶交互事件的響應。通常地,View會實現一個相應的接口;Presenter是通常充當Model和View的紐帶。緩存
其依賴關係爲:ide
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 }
/// <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; } } #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() { } }
/// <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 }
/// <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。ui
總結:this
由此,最大限度的將業務邏輯抽離到Presenter中處理,能夠將View的開發徹底獨立,只須要先將全部請求,規範成接口事件,客戶端邏輯本身實現,其餘邏輯經過事件與Presenter交互。 spa
模型與視圖徹底分離,咱們能夠修改視圖而不影響模型;能夠更高效地使用模型,由於全部的交互都發生在一個地方——Presenter內部; 設計