MVP開發模式的理解

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的主動性,我我的是這麼理解的:框架

  • Presenter是整個MVP體系的控制中心,而不是單純的處理View請求的人;
  • View僅僅是用戶交互請求的彙報者,對於響應用戶交互相關的邏輯和流程,View不參與決策,真正的決策者是Presenter;
  • View向Presenter發送用戶交互請求應該採用這樣的口吻:「我如今將用戶交互請求發送給你,你看着辦,須要個人時候我會協助你」,不該該是這樣:「我如今處理用戶交互請求了,我知道該怎麼辦,可是我須要你的支持,由於實現業務邏輯的Model只信任你」;
  • 對於綁定到View上的數據,不該該是View從Presenter上「拉」回來的,應該是Presenter主動「推」給View的;
  • View儘量不維護數據狀態,由於其自己僅僅實現單純的、獨立的UI操做;Presenter纔是整個體系的協調者,它根據處理用於交互的邏輯給View和Model安排工做。

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: }

引用自artech的文本

    1.談談關於MVP模式中V-P交互問題

    2.大話MVP

    3.MVC、MVP及Model

相關文章
相關標籤/搜索