• 浅谈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中,那么我们就可以脱离用户接口来测试这些逻辑(单元测试)

      

      

  • 相关阅读:
    springboot自动装配原理
    @Inherited 注解的作用
    基础知识点:链表反转
    基础知识点:二叉树的层次遍历
    算法题:8、二进制中1的个数
    微服务_GPRC
    扎实基础_数据结构
    .net core3.0程序发布到linux+docker
    c#_安全漏洞总结
    IIS Express(电脑无管理员权限如何启用VS调试)
  • 原文地址:https://www.cnblogs.com/YangFengHui/p/4601260.html
Copyright © 2020-2023  润新知