浅谈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; }
        }
        #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。

总结:

 由此,最大限度的将业务逻辑抽离到Presenter中处理,可以将View的开发完全独立,只需要先将所有请求,规范成接口事件,客户端逻辑自己实现,其他逻辑通过事件与Presenter交互。 

模型与视图完全分离,我们可以修改视图而不影响模型;可以更高效地使用模型,因为所有的交互都发生在一个地方——Presenter内部; 

如果我们把逻辑放在Presenter中,那么我们就可以脱离用户接口来测试这些逻辑(单元测试)

  

  

原文地址:https://www.cnblogs.com/YangFengHui/p/4601260.html