淺談MVP設計模式

  最近公司在作一個醫療項目,使用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


    }
基礎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; }
        }
        #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 = "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內部; 設計

若是咱們把邏輯放在Presenter中,那麼咱們就能夠脫離用戶接口來測試這些邏輯(單元測試)
相關文章
相關標籤/搜索