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