1.MVP是什么html
若是从层次关系来说,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的基本特征mvc
Passive View,顾名思义,View是被动的。那么主动是谁呢?答案是Presenter。对于Presenter的主动性,我我的是这么理解的:框架
3.理想与现实的距离ide
上面对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的编程模型this
如今,咱们就来若是经过一种简单的编程模式就可以让View对Presenter的依赖彻底地从中最终开发者的源代码中移除。为此,咱们须要定义一系列的基类,首先我为全部的View建立基类ViewBase,在这里咱们直接用Form做为View。ViewBase定义以下,为了使View中不能调用Presenter,我将其定义成私有字段。那么,如何让View和Presenter之间创建起关联呢?在这里经过虚方法CreatePresenter,具体的View必须重写该方法,否则会抛出一个NotImplementedException异常。在构造函数中,调用该方法为Presenter赋值。spa
1: using System; 2: using System.ComponentModell; 3: using System.Windows.Forms; 4: namespace MVPDemo 5: { 6: public class ViewBase: Form 7: { 8: private object _presenter; 9: 10: public ViewBase() 11: { 12: _presenter = this.CreatePresenter(); 13: } 14: 15: protected virtual object CreatePresenter() 16: { 17: if (LicenseManager.CurrentContext.UsageModel == LicenseUsageModel.Designtime) 18: { 19: return null; 20: } 21: else 22: { 23: throw new NotImplementedException(string.Format("{0} must override the CreatePresenter method.", this.GetType().FullName)); 24: } 25: } 26: } 27: }
而后,咱们也为全部的Presenter建立基类Presenter<IView>,泛型类型IView表示具体View实现的接口。表示View的同名只读属性在构造函数中赋值,赋值完成以后调用调用虚方法OnViewSet。具体的Presenter能够重写该方法进行对View进行事件注册工做。可是须要注意的是,Presenter的建立是在ViewBase的构造函数中经过调用CreatePresenter方法实现,因此执行OnViewSet的时候,View自己尚未彻底初始化,因此在此不能对View的控件进行操做。设计
1: namespace MVPDemo 2: { 3: public class Presenter<IView> 4: { 5: public IView View { get; private set; } 6: 7: public Presenter(IView view) 8: { 9: this.View = view; 10: this.OnViewSet(); 11: } 12: protected virtual void OnViewSet() 13: { } 14: } 15: }
因为,Presenter是经过接口的方式与View进行交互的。在这里,因为View经过Form的形式体现,有时候咱们要经过这个接口访问Form的一些属性、方法和事件,须要将相应的成员定义在接口上面,比较麻烦。此时,咱们能够选择将这些成员定义在一个接口中,具体View的接口继承该接口就能够了。在这里,咱们至关是为全部的View接口建立了“基接口”。做为演示,我如今了Form的三个事件成员定义在街口IViewBase中。
1: using System; 2: using System.ComponentModell; 3: namespace MVPDemo 4: { 5: public interface IViewBase 6: { 7: event EventHandler Load; 8: event EventHandler Closed; 9: event CancelEventHandler Closing; 10: } 11: }
5.实例演示
上面我经过定义基类和接口为整个编程模型搭建了一个框架,如今咱们经过一个具体的例子来介绍该编程模型的应用。咱们采用的是一个简单的Windows Forms应用,模拟管理客户信息的场景,逻辑很简单:程序启动的时候显示出全部的客户端列表;用户选择某一客户端,将响应的信息显示在TextBox中以供编辑;对客户端信息进行相应修改以后,点击OK按钮进行保存。整个操做界面以下图所示:
首先,咱们建立实体类Customer,简单起见,仅仅包含四个属性:Id、FirstName、LastName和Address:
1: using System; 2: namespace MVPDemo 3: { 4: public class Customer: ICloneable 5: { 6: public string Id 7: { get; set; } 8: 9: public string FirstName 10: { get; set; } 11: 12: public string LastName 13: { get; set; } 14: 15: public string Address 16: { get; set; } 17: 18: object ICloneable.Clone() 19: { 20: return this.Clone(); 21: } 22: 23: public Customer Clone() 24: { 25: return new Customer { 26: Id = this.Id, 27: FirstName = this.FirstName, 28: LastName = this.LastName, 29: Address = this.Address 30: }; 31: } 32: } 33: }
而后,为了真实模拟MVP三种角色,特地建立一个CustomerModel类型,实际上在真实的应用中,并无单独一个类型来表示Model。CustomerModel维护客户列表,体统相关的查询和更新操做。CustomerModel定义以下:
1: using System.Collections.Generic; 2: using System.Linq; 3: namespace MVPDemo 4: { 5: public class CustomerModel 6: { 7: private IList<Customer> _customers = new List<Customer>{ 8: new Customer{ Id = "001", FirstName = "San", LastName = "Zhang", Address="Su zhou"}, 9: new Customer{ Id = "002", FirstName = "Si", LastName = "Li", Address="Shang Hai"} 10: }; 11: 12: public void UpdateCustomer(Customer customer) 13: { 14: for (int i = 0; i < _customers.Count; i++) 15: { 16: if (_customers[i].Id == customer.Id) 17: { 18: _customers[i] = customer; 19: break; 20: } 21: } 22: } 23: 24: public Customer GetCustomerById(string id) 25: { 26: var customers = from customer in _customers 27: where customer.Id == id 28: select customer.Clone(); 29: return customers.ToArray<Customer>()[0]; 30: } 31: 32: public Customer[] GetAllCustomers() 33: { 34: var customers = from customer in _customers 35: select customer.Clone(); 36: return customers.ToArray<Customer>(); 37: } 38: } 39: }
接着,咱们定义View的接口ICustomerView。ICustomerView定义了两个事件,CustomerSelected在用户从Gird中选择了某个条客户记录是触发,而CustomerSaving则在用户完成编辑点击OK按钮视图提交修改时触发。ICustomerView还定义了View必须完成的三个基本操做:绑定客户列表(ListAllCustomers);显示单个客户信息到TextBox(DisplayCustomerInfo);保存后清空可编辑控件(Clear)。
1: using System; 2: namespace MVPDemo 3: { 4: public interface ICustomerView : IViewBase 5: { 6: event EventHandler<CustomerEventArgs> CustomerSelected; 7: 8: event EventHandler<CustomerEventArgs> CustomerSaving; 9: 10: void ListAllCustomers(Customer[] customers); 11: 12: void DisplayCustomerInfo(Customer customer); 13: 14: void Clear(); 15: } 16: }
事件参数的类型CustomerEventArgs定义以下,两个属性CustomerId和Customer分别表明客户ID和具体的客户,它们分别用于上面提到的CustomerSelected和CustomerSaving事件。
1: using System; 2: namespace MVPDemo 3: { 4: public class CustomerEventArgs : EventArgs 5: { 6: public string CustomerId 7: { get; set; } 8: 9: public Customer Customer 10: { get; set; } 11: } 12: }
而具体的Presenter定义在以下的CustomerPresenter类型中。在重写的OnViewSet方法中注册View的三个事件:Load事件中调用Model获取全部客户列表,并显示在View的Grid上;CustomerSelected事件中经过事件参数传递的客户ID调用Model获取相应的客户信息,显示在View的可编辑控件上;CustomerSaving则经过事件参数传递的被更新过的客户信息,调用Model提交更新。
1: using System.Windows.Forms; 2: 3: namespace MVPDemo 4: { 5: public class CustomerPresenter: Presenter<ICustomerView> 6: { 7: public CustomerModel Model 8: { get; private set; } 9: 10: public CustomerPresenter(ICustomerView view) 11: : base(view) 12: { 13: this.Model = new CustomerModel(); 14: } 15: 16: protected override void OnViewSet() 17: { 18: this.View.Load += (sender, args) => 19: { 20: Customer[] customers = this.Model.GetAllCustomers(); 21: this.View.ListAllCustomers(customers); 22: this.View.Clear(); 23: }; 24: this.View.CustomerSelected += (sender, args) => 25: { 26: Customer customer = this.Model.GetCustomerById(args.CustomerId); 27: this.View.DisplayCustomerInfo(customer); 28: }; 29: this.View.CustomerSaving += (sender, args) => 30: { 31: this.Model.UpdateCustomer(args.Customer); 32: Customer[] customers = this.Model.GetAllCustomers(); 33: this.View.ListAllCustomers(customers); 34: this.View.Clear();36: }; 37: } 38: } 39: }
对于具体的View来讲,仅仅须要实现ICustomerView,并处理响应控件事件便可(主要是用户从Grid中选择某个记录触发的RowHeaderMouseClick事件,以及点击OK的事件)。实际上不须要View亲自处理这些事件,而仅仅须要触发相应的事件,让事件订阅者(Presenter)来处理就能够了。此外还须要重写CreatePresenter方法完成对CustomerPresenter的建立。CustomerView定义以下:
1: using System; 2: using System.Windows.Forms; 3: 4: namespace MVPDemo 5: { 6: public partial class CustomerView : ViewBase, ICustomerView 7: { 8: public CustomerView() 9: { 10: InitializeComponent(); 11: } 12: 13: protected override object CreatePresenter() 14: { 15: return new CustomerPresenter(this); 16: } 17: 18: #region ICustomerView Members 19: 20: public event EventHandler<CustomerEventArgs> CustomerSelected; 21: 22: public event EventHandler<CustomerEventArgs> CustomerSaving; 23: 24: public void ListAllCustomers(Customer[] customers) 25: { 26: this.dataGridViewCustomers.DataSource = customers; 27: } 28: 29: public void DisplayCustomerInfo(Customer customer) 30: { 31: this.buttonOK.Enabled = true; 32: this.textBoxId.Text = customer.Id; 33: this.textBox1stName.Text = customer.FirstName; 34: this.textBoxLastName.Text = customer.LastName; 35: this.textBoxAddress.Text = customer.Address; 36: } 37: 38: public void Clear() 39: { 40: this.buttonOK.Enabled = false; 41: this.textBox1stName.Text = string.Empty; 42: this.textBoxLastName.Text = string.Empty; 43: this.textBoxAddress.Text = string.Empty; 44: this.textBoxId.Text = string.Empty; 45: } 46: 47: #endregion 48: 49: protected virtual void OnCustomerSelected(string customerId) 50: { 51: var previousId = this.textBoxId.Text.Trim(); 52: if (customerId == previousId) 53: { 54: return; 55: } 56: if(null != this.CustomerSelected) 57: { 58: this.CustomerSelected(this, new CustomerEventArgs{ CustomerId = customerId}); 59: } 60: } 61: 62: protected virtual void OnCustomerSaving(Customer customer) 63: { 64: if(null != this.CustomerSaving) 65: { 66: this.CustomerSaving(this, new CustomerEventArgs{ Customer = customer}); 67: } 68: } 69: 70: private void dataGridViewCustomers_RowHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e) 71: { 72: var currentRow = this.dataGridViewCustomers.Rows[e.RowIndex]; 73: var customerId = currentRow.Cells[0].Value.ToString(); 74: this.OnCustomerSelected(customerId); 75: } 76: 77: private void buttonOK_Click(object sender, EventArgs e) 78: { 79: var customer = new Customer(); 80: customer.Id = this.textBoxId.Text.Trim(); 81: customer.FirstName = this.textBox1stName.Text.Trim(); 82: customer.LastName = this.textBoxLastName.Text.Trim(); 83: customer.Address = this.textBoxAddress.Text.Trim(); 84: this.OnCustomerSaving(customer); 85: } 86: } 87: }
2.大话MVP