MVP开发模式的理解
1.MVP是什么
如果从层次关系来讲,MVP属于Presentation层的设计模式。对于一个UI模块来说,它的所有功能被分割为三个部分,分别通过Model、View和Presenter来承载。Model、View和Presenter相互协作,完成对最初数据的呈现和对用户操作的响应,它们具有各自的职责划分。Model可以看成是模块的业务逻辑和数据的提供者;View专门负责数据可视化的呈现,和用户交互事件的相对应。一般地,View会实现一个相应的接口;Presenter是一般充当Model和View的纽带。
MVP具有很多的变体,其中最为常用的一种变体成为Passive View(被动视图)。对于Passive View,Model、View和Presenter之间的关系如下图所示。View和Modell之间不能直接交互,View通过Presenter与Model打交道。Presenter接受View的UI请求,完成简单的UI处理逻辑,并调用Model进行业务处理,并调用View将相应的结果反映出来。View直接依赖Presenter,但是Presenter间接依赖View,它直接依赖的是View实现的接口。
2.Passive View的基本特征
Passive View,顾名思义,View是被动的。那么主动是谁呢?答案是Presenter。对于Presenter的主动性,我个人是这么理解的:
- Presenter是整个MVP体系的控制中心,而不是单纯的处理View请求的人;
- View仅仅是用户交互请求的汇报者,对于响应用户交互相关的逻辑和流程,View不参与决策,真正的决策者是Presenter;
- View向Presenter发送用户交互请求应该采用这样的口吻:“我现在将用户交互请求发送给你,你看着办,需要我的时候我会协助你”,不应该是这样:“我现在处理用户交互请求了,我知道该怎么办,但是我需要你的支持,因为实现业务逻辑的Model只信任你”;
- 对于绑定到View上的数据,不应该是View从Presenter上“拉”回来的,应该是Presenter主动“推”给View的;
- View尽可能不维护数据状态,因为其本身仅仅实现单纯的、独立的UI操作;Presenter才是整个体系的协调者,它根据处理用于交互的逻辑给View和Model安排工作。
3.理想与现实的距离
上面对Passive View MVP特征的罗列,我觉得是一种理想状态。是在大型项目中,尤其是项目的开发者自身并不完全理解MVP原理的情况下,要整体实现这样的一种理想状态是一件很难的事情。从Passive View中Model、View和Presenter三者之间的依赖关系来看,这个模型充分地给了开发者犯这样错误的机会。注意上面的图中View到Presenter的箭头表明View是可以任意的调用Presenter的。开发人员完全有可能将大部分UI处理逻辑写在View中,而Presenter仅仅对Model响应操作的简单调用。为了杜绝开发人员将程序写成基于Proxy的MVP,在我看来,唯一的办法就是尽量弱化(不可能剔除)View对Presenter的依赖。实际上,对于MVP来说,View仅仅向Presenter递交用户交互请求,仅此而已。如果我们将View对Presenter的这点依赖关系实现在框架层次中,最终开发人员的编程来说就不需要这种依赖了。那么我就可以通过一定的编程技巧使View根本无法访问Presenter,从而避免Presenter成为Proxy的可能的。那么,如果在不能获得Presenter的情况下,使View能够正常将请求递交给Presenter呢?很简单,通过事件订阅机制就可以了,虽然View不可以获取到Presenter,但是Presenter却可以获取到View,让Presenter订阅View的相关事件就可以的。
4.让View不再依赖于Presenter的编程模型
现在,我们就来如果通过一种简单的编程模式就能够让View对Presenter的依赖完全地从中最终开发者的源代码中移除。为此,我们需要定义一系列的基类,首先我为所有的View创建基类ViewBase,在这里我们直接用Form作为View。ViewBase定义如下,为了使View中不能调用Presenter,我将其定义成私有字段。那么,如何让View和Presenter之间建立起关联呢?在这里通过虚方法CreatePresenter,具体的View必须重写该方法,不然会抛出一个NotImplementedException异常。在构造函数中,调用该方法为Presenter赋值。
: using System;
: using System.ComponentModell;
: using System.Windows.Forms;
: namespace MVPDemo
: {
: public class ViewBase: Form
: {
: private object _presenter;
:
: public ViewBase()
: {
: _presenter = this.CreatePresenter();
: }
:
: protected virtual object CreatePresenter()
: {
: if (LicenseManager.CurrentContext.UsageModel == LicenseUsageModel.Designtime)
: {
: return null;
: }
: else
: {
: throw new NotImplementedException(string.Format("{0} must override the CreatePresenter method.", this.GetType().FullName));
: }
: }
: }
: }
然后,我们也为所有的Presenter创建基类Presenter<IView>,泛型类型IView表示具体View实现的接口。表示View的同名只读属性在构造函数中赋值,赋值完成之后调用调用虚方法OnViewSet。具体的Presenter可以重写该方法进行对View进行事件注册工作。但是需要注意的是,Presenter的创建是在ViewBase的构造函数中通过调用CreatePresenter方法实现,所以执行OnViewSet的时候,View本身还没有完全初始化,所以在此不能对View的控件进行操作。
: namespace MVPDemo
: {
: public class Presenter<IView>
: {
: public IView View { get; private set; }
:
: public Presenter(IView view)
: {
: this.View = view;
: this.OnViewSet();
: }
: protected virtual void OnViewSet()
: { }
: }
: }
由于,Presenter是通过接口的方式与View进行交互的。在这里,由于View通过Form的形式体现,有时候我们要通过这个接口访问Form的一些属性、方法和事件,需要将相应的成员定义在接口上面,比较麻烦。此时,我们可以选择将这些成员定义在一个接口中,具体View的接口继承该接口就可以了。在这里,我们相当是为所有的View接口创建了“基接口”。作为演示,我现在了Form的三个事件成员定义在街口IViewBase中。
: using System;
: using System.ComponentModell;
: namespace MVPDemo
: {
: public interface IViewBase
: {
: event EventHandler Load;
: event EventHandler Closed;
: event CancelEventHandler Closing;
: }
: }
5.实例演示
上面我通过定义基类和接口为整个编程模型搭建了一个框架,现在我们通过一个具体的例子来介绍该编程模型的应用。我们采用的是一个简单的Windows Forms应用,模拟管理客户信息的场景,逻辑很简单:程序启动的时候显示出所有的客户端列表;用户选择某一客户端,将响应的信息显示在TextBox中以供编辑;对客户端信息进行相应修改之后,点击OK按钮进行保存。整个操作界面如下图所示:
首先,我们创建实体类Customer,简单起见,仅仅包含四个属性:Id、FirstName、LastName和Address:
: using System;
: namespace MVPDemo
: {
: public class Customer: ICloneable
: {
: public string Id
: { get; set; }
:
: public string FirstName
: { get; set; }
:
: public string LastName
: { get; set; }
:
: public string Address
: { get; set; }
:
: object ICloneable.Clone()
: {
: return this.Clone();
: }
:
: public Customer Clone()
: {
: return new Customer {
: Id = this.Id,
: FirstName = this.FirstName,
: LastName = this.LastName,
: Address = this.Address
: };
: }
: }
: }
然后,为了真实模拟MVP三种角色,特意创建一个CustomerModel类型,实际上在真实的应用中,并没有单独一个类型来表示Model。CustomerModel维护客户列表,体统相关的查询和更新操作。CustomerModel定义如下:
: using System.Collections.Generic;
: using System.Linq;
: namespace MVPDemo
: {
: public class CustomerModel
: {
: private IList<Customer> _customers = new List<Customer>{
: new Customer{ Id = "", FirstName = "San", LastName = "Zhang", Address="Su zhou"},
: new Customer{ Id = "", FirstName = "Si", LastName = "Li", Address="Shang Hai"}
: };
:
: public void UpdateCustomer(Customer customer)
: {
: for (int i = ; i < _customers.Count; i++)
: {
: if (_customers[i].Id == customer.Id)
: {
: _customers[i] = customer;
: break;
: }
: }
: }
:
: public Customer GetCustomerById(string id)
: {
: var customers = from customer in _customers
: where customer.Id == id
: select customer.Clone();
: return customers.ToArray<Customer>()[];
: }
:
: public Customer[] GetAllCustomers()
: {
: var customers = from customer in _customers
: select customer.Clone();
: return customers.ToArray<Customer>();
: }
: }
: }
接着,我们定义View的接口ICustomerView。ICustomerView定义了两个事件,CustomerSelected在用户从Gird中选择了某个条客户记录是触发,而CustomerSaving则在用户完成编辑点击OK按钮视图提交修改时触发。ICustomerView还定义了View必须完成的三个基本操作:绑定客户列表(ListAllCustomers);显示单个客户信息到TextBox(DisplayCustomerInfo);保存后清空可编辑控件(Clear)。
: using System;
: namespace MVPDemo
: {
: public interface ICustomerView : IViewBase
: {
: event EventHandler<CustomerEventArgs> CustomerSelected;
:
: event EventHandler<CustomerEventArgs> CustomerSaving;
:
: void ListAllCustomers(Customer[] customers);
:
: void DisplayCustomerInfo(Customer customer);
:
: void Clear();
: }
: }
事件参数的类型CustomerEventArgs定义如下,两个属性CustomerId和Customer分别代表客户ID和具体的客户,它们分别用于上面提到的CustomerSelected和CustomerSaving事件。
: using System;
: namespace MVPDemo
: {
: public class CustomerEventArgs : EventArgs
: {
: public string CustomerId
: { get; set; }
:
: public Customer Customer
: { get; set; }
: }
: }
而具体的Presenter定义在如下的CustomerPresenter类型中。在重写的OnViewSet方法中注册View的三个事件:Load事件中调用Model获取所有客户列表,并显示在View的Grid上;CustomerSelected事件中通过事件参数传递的客户ID调用Model获取相应的客户信息,显示在View的可编辑控件上;CustomerSaving则通过事件参数传递的被更新过的客户信息,调用Model提交更新。
: using System.Windows.Forms;
:
: namespace MVPDemo
: {
: public class CustomerPresenter: Presenter<ICustomerView>
: {
: public CustomerModel Model
: { get; private set; }
:
: public CustomerPresenter(ICustomerView view)
: : base(view)
: {
: this.Model = new CustomerModel();
: }
:
: protected override void OnViewSet()
: {
: this.View.Load += (sender, args) =>
: {
: Customer[] customers = this.Model.GetAllCustomers();
: this.View.ListAllCustomers(customers);
: this.View.Clear();
: };
: this.View.CustomerSelected += (sender, args) =>
: {
: Customer customer = this.Model.GetCustomerById(args.CustomerId);
: this.View.DisplayCustomerInfo(customer);
: };
: this.View.CustomerSaving += (sender, args) =>
: {
: this.Model.UpdateCustomer(args.Customer);
: Customer[] customers = this.Model.GetAllCustomers();
: this.View.ListAllCustomers(customers);
: this.View.Clear();: };
: }
: }
: }
对于具体的View来说,仅仅需要实现ICustomerView,并处理响应控件事件即可(主要是用户从Grid中选择某个记录触发的RowHeaderMouseClick事件,以及点击OK的事件)。实际上不需要View亲自处理这些事件,而仅仅需要触发相应的事件,让事件订阅者(Presenter)来处理就可以了。此外还需要重写CreatePresenter方法完成对CustomerPresenter的创建。CustomerView定义如下:
: using System;
: using System.Windows.Forms;
:
: namespace MVPDemo
: {
: public partial class CustomerView : ViewBase, ICustomerView
: {
: public CustomerView()
: {
: InitializeComponent();
: }
:
: protected override object CreatePresenter()
: {
: return new CustomerPresenter(this);
: }
:
: #region ICustomerView Members
:
: public event EventHandler<CustomerEventArgs> CustomerSelected;
:
: public event EventHandler<CustomerEventArgs> CustomerSaving;
:
: public void ListAllCustomers(Customer[] customers)
: {
: this.dataGridViewCustomers.DataSource = customers;
: }
:
: public void DisplayCustomerInfo(Customer customer)
: {
: this.buttonOK.Enabled = true;
: this.textBoxId.Text = customer.Id;
: this.textBox1stName.Text = customer.FirstName;
: this.textBoxLastName.Text = customer.LastName;
: this.textBoxAddress.Text = customer.Address;
: }
:
: public void Clear()
: {
: this.buttonOK.Enabled = false;
: this.textBox1stName.Text = string.Empty;
: this.textBoxLastName.Text = string.Empty;
: this.textBoxAddress.Text = string.Empty;
: this.textBoxId.Text = string.Empty;
: }
:
: #endregion
:
: protected virtual void OnCustomerSelected(string customerId)
: {
: var previousId = this.textBoxId.Text.Trim();
: if (customerId == previousId)
: {
: return;
: }
: if(null != this.CustomerSelected)
: {
: this.CustomerSelected(this, new CustomerEventArgs{ CustomerId = customerId});
: }
: }
:
: protected virtual void OnCustomerSaving(Customer customer)
: {
: if(null != this.CustomerSaving)
: {
: this.CustomerSaving(this, new CustomerEventArgs{ Customer = customer});
: }
: }
:
: private void dataGridViewCustomers_RowHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
: {
: var currentRow = this.dataGridViewCustomers.Rows[e.RowIndex];
: var customerId = currentRow.Cells[].Value.ToString();
: this.OnCustomerSelected(customerId);
: }
:
: private void buttonOK_Click(object sender, EventArgs e)
: {
: var customer = new Customer();
: customer.Id = this.textBoxId.Text.Trim();
: customer.FirstName = this.textBox1stName.Text.Trim();
: customer.LastName = this.textBoxLastName.Text.Trim();
: customer.Address = this.textBoxAddress.Text.Trim();
: this.OnCustomerSaving(customer);
: }
: }
: }
引用自artech的文本
2.大话MVP
MVP开发模式的理解的更多相关文章
- 理解 Android MVP 开发模式
/***************************************************************************************** * 理解 Andr ...
- Android应用中MVP开发模式
所谓MVP(Model-View-Presenter)模式.是将APP的结构分为三层: view - UI显示层 view 层主要负责: 提供UI交互 在presenter的控制下修改UI. 将业务事 ...
- Android MVP开发模式及Retrofit + RxJava封装
代码已上传到Github,因为接口都是模拟无法进行测试,明白大概的逻辑就行了! 欢迎浏览我的博客--https://pushy.site 1. MVP模式 1.1 介绍 如果熟悉MVP模式架构的话,对 ...
- java web学习总结(二十九) -------------------JavaBean的两种开发模式
SUN公司推出JSP技术后,同时也推荐了两种web应用程序的开发模式,一种是JSP+JavaBean模式,一种是Servlet+JSP+JavaBean模式. 一.JSP+JavaBean开发模式 1 ...
- javaweb学习总结(二十一)——JavaWeb的两种开发模式
SUN公司推出JSP技术后,同时也推荐了两种web应用程序的开发模式,一种是JSP+JavaBean模式,一种是Servlet+JSP+JavaBean模式. 一.JSP+JavaBean开发模式 1 ...
- 咸鱼入门到放弃10--javaweb的两种开发模式
(本篇是之前方法的综合使用,新东西不多,其中也涉及三层架构的问题.此处直接引用了大佬blog:https://www.cnblogs.com/xdp-gacl/p/3908610.html) SUN公 ...
- JavaWeb学习 (二十)————JavaWeb的两种开发模式
一.JSP+JavaBean开发模式 1.1.jsp+javabean开发模式架构 jsp+javabean开发模式的架构图如下图(图1-1)所示
- javaweb(二十一)——JavaWeb的两种开发模式
一.JSP+JavaBean开发模式 1.1.jsp+javabean开发模式架构 jsp+javabean开发模式的架构图如下图(图1-1)所示
- javaweb基础(21)_两种开发模式
SUN公司推出JSP技术后,同时也推荐了两种web应用程序的开发模式,一种是JSP+JavaBean模式,一种是Servlet+JSP+JavaBean模式. 一.JSP+JavaBean开发模式 1 ...
随机推荐
- C++编译错误杂记
目录 2018年12月23日 error: no matching function for call to ××× 2018年12月10日 error: expected ')' before '* ...
- Qt——父对象、布局
设置父对象两个好处:(1)加入析构树(2)和父对象一起显示 设置布局后,子控件自动被设置父对象 设置父对象两个好处:(1)加入析构树(2)和父对象一起显示
- sigmoid function和softmax function
sigmoid函数(也叫逻辑斯谛函数): 引用wiki百科的定义: A logistic function or logistic curve is a common “S” shape (sigm ...
- 【LG4735】最大异或和
[LG4735]最大异或和 题意 洛谷 题解 维护一个前缀异或和\(S_i\) 对于一个询问操作\(l\).\(r\).\(x\) 就是等价于求一个位置\(p\)(\(l\leq p \leq r)\ ...
- Mac OS下Android Studio:/dev/kvm not found
在配置模拟器时出现该报错,在网上找了很多教程都没能解决,当然可能是这些教程并不适用于我.总的来说,还是要“对症下药”! 解决方法如下: 点击“系统偏好设置”-“安全性与隐私”,然后会在“通用”这个界面 ...
- gitlab改root密码
1. ~$ sudo gitlab-rails console production 2.查询要改的用户 irb(main)::> u = User.where().first => #& ...
- Xuan.UWP.Framework(2)
上一章主要介绍了Xuan.UWP.Framework.ImageLib的基本用法,这一章具体来看些Xuan.UWP.Framework.ImageLib的使用. 一.首先看下Xuan.UWP.Fram ...
- 微信小程序学习笔记(1)-微信小程序样式设置逻辑
1.微信小程序的样式设置统一在每一页的.wxss的样式文件中,所有的样式设置代码统一写入这个文件中: 2.样式主要是通过.wxml里面控件的“class”属性来调用,此处调用会有几个细节要注意: 1) ...
- 「国庆训练」Kingdom of Obsession(HDU-5943)
题意 给定\(s,n\),把\(s+1,s+2,...,s+n\)这\(n\)个数填到\(1,2,...,n\)里,要求\(x\)只能填到\(x\)的因子的位置(即题目中\(x\%y=0\)那么x才能 ...
- Qt-QML-Charts-ChartView-编译错误-ASSERT: "!"No style available without QApplication!
昨天本来是回家想好好琢磨一下使用Chart来绘制曲线的,奈何在建立项目的时候也就卡住了,加上心情比较烦躁,也没有耐心寻找答案就草草了事.所以今天继续搞定这个. 上图是Qt 的编译错误截图 QML de ...