乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      設(shè)計(jì)模式:Model View Presenter

       xiao huan 2007-12-10

      設(shè)計(jì)模式:Model View Presenter

      發(fā)布日期: 2006-08-07 | 更新日期: 2006-08-07

      Jean-Paul Boodhoo

      下載本文的代碼:DesignPatterns2006_08.exe (4423KB)

      *
      本頁(yè)內(nèi)容

      隨著 UI 創(chuàng)建技術(shù)(如 ASP.NET 和 Windows® Form)的功能越來(lái)越強(qiáng)大,讓 UI 層執(zhí)行更多功能已成為普遍的做法。由于沒(méi)有清晰的職責(zé)劃分,UI 層經(jīng)常成為邏輯層的全能代理,而后者實(shí)際上屬于應(yīng)用程序的其他層。Model View Presenter (MVP) 模式是專(zhuān)門(mén)適用于解決此問(wèn)題的一種設(shè)計(jì)模式。為了證明我的觀點(diǎn),我將遵循 MVP 模式為 Northwind 數(shù)據(jù)庫(kù)中的客戶(hù)創(chuàng)建一個(gè)顯示屏。

      為什么 UI 層中不應(yīng)有過(guò)多邏輯?如果沒(méi)有手動(dòng)運(yùn)行應(yīng)用程序,或未能維護(hù)自動(dòng)執(zhí)行 UI 組件的高深 UI 運(yùn)行程序腳本,則很難測(cè)試應(yīng)用程序 UI 層中的代碼。這本身就是一個(gè)麻煩事,而更大的麻煩是應(yīng)用程序中普通視圖間大量的重復(fù)代碼。當(dāng)在 UI 層的不同部分之間復(fù)制執(zhí)行特定業(yè)務(wù)功能的邏輯時(shí),通常很難發(fā)現(xiàn)好的重構(gòu)候選者。MVP 設(shè)計(jì)模式使得將邏輯和代碼從 UI 層分離更為輕松,從而更易于簡(jiǎn)化測(cè)試可重用代碼。

      圖 1 顯示組成示例應(yīng)用程序的主要層。請(qǐng)注意 UI 層和表示層使用不同的軟件包。您可能期望它們使用相同的軟件包,但實(shí)際上一個(gè)項(xiàng)目的 UI 層只應(yīng)由兩種 UI 元素組成 — 窗體和控件。在 Web Forms 項(xiàng)目中,通常是 ASP.NET Web Forms、用戶(hù)控件和服務(wù)器控件的集合。在 Windows Forms 中,是 Windows Forms、用戶(hù)控件和第三方程序庫(kù)的集合。此附加層用于分離顯示和邏輯。在表示層中可以有實(shí)際實(shí)現(xiàn) UI 行為的對(duì)象,如驗(yàn)證顯示、UI 的集合輸入等。

      a

      圖 1 應(yīng)用程序體系結(jié)構(gòu)

      遵循 MVP

      如圖 2 所示,此項(xiàng)目的 UI 是非常標(biāo)準(zhǔn)的。加載頁(yè)面時(shí),屏幕將會(huì)顯示一個(gè)填充了 Northwind 數(shù)據(jù)庫(kù)中所有客戶(hù)的下拉框。如果您從下拉列表中選擇一個(gè)客戶(hù),將會(huì)更新頁(yè)面,以顯示該客戶(hù)的信息。通過(guò)遵循 MVP 設(shè)計(jì)模式,您可將各種行為從 UI 層分離,將其置入自身的類(lèi)中。圖 3 顯示一個(gè)類(lèi)圖表,表示涉及的不同類(lèi)之間的關(guān)聯(lián)。

      a

      圖 2 客戶(hù)信息

      需要注意的很重要的一點(diǎn)是,表示器并不了解應(yīng)用程序?qū)嶋H UI 層的任何知識(shí)。它知道它可以與接口對(duì)話,但不知道也不關(guān)心接口的具體實(shí)現(xiàn)。這就促使了在不同 UI 技術(shù)間表示器的重用。

      我將使用測(cè)試驅(qū)動(dòng)開(kāi)發(fā) (TDD) 來(lái)創(chuàng)建客戶(hù)屏幕功能。圖 4 顯示我將使用的第一個(gè)測(cè)試的詳細(xì)信息,以說(shuō)明我期望在頁(yè)面加載上觀察到的行為。TDD 使我可以一次將精力集中于一個(gè)問(wèn)題,只編寫(xiě)可使測(cè)試通過(guò)的足夠代碼,然后再繼續(xù)進(jìn)行。在此測(cè)試中,我將利用一個(gè)名為 NMock2 的模擬對(duì)象框架來(lái)構(gòu)建接口的模擬實(shí)現(xiàn)。

      a

      圖 3 MVP 類(lèi)圖表

      在我的 MVP 實(shí)現(xiàn)中,我決定將表示器作為其將要配合工作的視圖的附屬。在能使對(duì)象立即工作的狀態(tài)下創(chuàng)建對(duì)象總是很好的。在此應(yīng)用程序中,表示層實(shí)際上是依靠服務(wù)層來(lái)調(diào)用域功能的。由于此需求,因此也有必要建立一個(gè)帶接口的表示器,通過(guò)該接口它可以與服務(wù)類(lèi)進(jìn)行對(duì)話。這將確保一旦建立表示器后,它就可以進(jìn)行所有需要它來(lái)完成的工作。我將通過(guò)創(chuàng)建兩個(gè)特定的模擬開(kāi)始:一個(gè)用于服務(wù)層,一個(gè)用于表示器將要使用的視圖。

      為什么要?jiǎng)?chuàng)建模擬?單元測(cè)試的規(guī)則是盡可能的隔離測(cè)試,以將精力集中于一個(gè)特定的對(duì)象。在此測(cè)試中,我只關(guān)注表示器的預(yù)期行為。此時(shí),我并不在意視圖接口或服務(wù)接口的實(shí)際實(shí)現(xiàn),我相信那些接口定義的協(xié)議,并相應(yīng)的設(shè)置模擬來(lái)表現(xiàn)。這可確保我將測(cè)試集中于我所期望的表示器行為,無(wú)需考慮其所依賴(lài)的對(duì)象。調(diào)用其初始化方法后,我所期望的表示器行為如下。

      首先,表示器應(yīng)調(diào)用 ICustomerTask 服務(wù)層對(duì)象上的 GetCustomerList 方法(在測(cè)試中模擬)。請(qǐng)注意您可以使用 NMock 模仿模擬的行為。而對(duì)于服務(wù)層,我希望它可將模擬 ILookupCollection 返回到表示器。然后,在表示器從服務(wù)層檢索 ILookupCollection 后,它應(yīng)調(diào)用集合的 BindTo 方法并將方法傳遞到 ILookupList 的實(shí)現(xiàn)。通過(guò)使用 NMockExpect.Once 方法,我可以確定如果表示器沒(méi)有調(diào)用該方法一次(且僅一次),則測(cè)試將失敗。

      編寫(xiě)該測(cè)試后,我將會(huì)處于完全非編輯狀態(tài)。我將盡可能做最簡(jiǎn)單的工作來(lái)使測(cè)試通過(guò)。

      使第一次測(cè)試通過(guò)

      首先編寫(xiě)測(cè)試的好處之一是我現(xiàn)在擁有了一個(gè)遠(yuǎn)景藍(lán)圖,可以遵循它來(lái)對(duì)測(cè)試進(jìn)行編譯并最終通過(guò)。第一次測(cè)試包括兩個(gè)還不存在的接口。這些接口是正確編譯代碼的先決條件。我將從 IViewCustomerView 的代碼開(kāi)始:

      public interface IViewCustomerView
      {
      ILookupList CustomerList { get; }
      }
      

      此接口提供一個(gè)屬性,該屬性可返回一個(gè) ILookupList 接口實(shí)現(xiàn)。對(duì)于該問(wèn)題,我還沒(méi)有一個(gè) ILookupList 接口,甚至沒(méi)有實(shí)施工具。為了通過(guò)此測(cè)試,我不需要明確的實(shí)施工具,這樣我可以繼續(xù)創(chuàng)建 ILookupList 接口:?

      public interface ILookupList { }
      

      此時(shí),ILookupList 接口看起來(lái)沒(méi)什么用處。我的目標(biāo)是編譯并通過(guò)測(cè)試,而這些接口可以滿(mǎn)足測(cè)試的需求?,F(xiàn)在該將焦點(diǎn)轉(zhuǎn)向我要實(shí)際測(cè)試的對(duì)象 - ViewCustomerPresenter 了。?此類(lèi)尚不存在,但回頭查看該測(cè)試,您可以從中得出兩個(gè)重要事實(shí):它有一個(gè)構(gòu)造函數(shù),該函數(shù)需要視圖和服務(wù)實(shí)現(xiàn)作為依賴(lài),并且有一個(gè)空的 Initialize 方法。圖 5 中的代碼顯示如何編譯測(cè)試。

      請(qǐng)牢記表示器需要其所有依賴(lài)關(guān)系,以便富有成效的進(jìn)行工作;這就是傳入視圖和服務(wù)的原因。我沒(méi)有實(shí)現(xiàn)初始化方法,因此如果運(yùn)行測(cè)試,我將得到 NotImplementedException。

      如上所述,我沒(méi)有盲目的編寫(xiě)表示器代碼;通過(guò)查看測(cè)試,我已了解在調(diào)用初始化方法后表示器應(yīng)表現(xiàn)的行為。行為的實(shí)現(xiàn)代碼如下:

      public void Initialize()
      {
      task.GetCustomerList().BindTo(view.CustomerList);
      }
      

      本文附帶的源代碼中有 CustomerTask 類(lèi)(實(shí)現(xiàn)了 ICustomerTask 接口)中 GetCustomerList 方法的完整實(shí)現(xiàn)。雖然從實(shí)現(xiàn)和測(cè)試表示器的角度看,我還無(wú)需了解是否存在工作實(shí)現(xiàn)。但正是該抽象級(jí)別使我難以通過(guò)表示器類(lèi)的測(cè)試。第一個(gè)測(cè)試現(xiàn)在正處于將要編譯和運(yùn)行的狀態(tài)。這證明在調(diào)用表示器上的 Initialize 方法時(shí),它將以我在測(cè)試中指定的方式與其依賴(lài)對(duì)象進(jìn)行交互,并且最終當(dāng)這些依賴(lài)對(duì)象的具體實(shí)現(xiàn)被插入表示器時(shí),我可以確信結(jié)果視圖(ASPX 頁(yè))將被客戶(hù)列表所填充。

      填充 DropDownList

      到目前為止,我主要處理了接口,拋開(kāi)實(shí)際的實(shí)現(xiàn)細(xì)節(jié),將精力集中于表示器。現(xiàn)在,該建立一些探測(cè)代碼了,它最終將允許表示器以一種可測(cè)試的方式在 Web 頁(yè)面上填充列表。實(shí)現(xiàn)此功能的關(guān)鍵是將在 LookupCollection 類(lèi)的 BindTo 方法中發(fā)生的交互。如果您看一下圖 6 中 LookupCollection 類(lèi)的實(shí)現(xiàn),就會(huì)注意到它實(shí)現(xiàn)了 ILookupCollection 接口。本文的源代碼帶有隨附測(cè)試,可用于建立 LookupCollection 類(lèi)的功能。

      BindTo 方法的實(shí)現(xiàn)特別有趣。請(qǐng)注意在此方法中,集合將重復(fù) ILookupDTO 實(shí)現(xiàn)本身的私有列表。ILookupDTO 是一個(gè)接口,可很好地與 UI 層的組合框綁定:

      public interface ILookupDTO
      {
      string Value { get; }
      string Text { get; }
      }
      

      圖 7 顯示用于測(cè)試查找集合的 BindTo 方法的代碼,此方法將會(huì)幫助解釋 LookupCollection 與 ILookupList 之間的預(yù)期交互。最后一點(diǎn)特別有趣。在此測(cè)試中,我希望在嘗試向列表添加項(xiàng)目前,LookupCollection 將會(huì)調(diào)用 ILookupList 實(shí)現(xiàn)中的 Clear 方法。然后,我希望可以在 ILookupList 上調(diào)用 Add 10 次,而作為 Add 方法的參數(shù),LookupCollection 將在實(shí)現(xiàn) ILookupDTO 接口的對(duì)象中傳遞。若要使其與 Web 項(xiàng)目中的控件(例如下拉列表框)配合使用,則您需要?jiǎng)?chuàng)建一個(gè) ILookupList 實(shí)現(xiàn),該實(shí)現(xiàn)知道如何與 Web 項(xiàng)目中的控件配合使用。

      本文附帶的源代碼包含一個(gè)名為 MVP.Web.Controls 的項(xiàng)目。該項(xiàng)目包含我選擇用于創(chuàng)建完整解決方案的所有 Web 特定控件或類(lèi)。為什么我將代碼放在此項(xiàng)目中,而不是放在 APP_CODE 目錄或 Web 項(xiàng)目中?回答是可測(cè)試性。在沒(méi)有手動(dòng)運(yùn)行應(yīng)用程序或沒(méi)有使用某種測(cè)試程序自動(dòng)執(zhí)行 UI 測(cè)試的情況下,很難直接測(cè)試 Web 項(xiàng)目中的任何控件。MVP 模式使我可在不必手動(dòng)運(yùn)行應(yīng)用程序的情況下考慮更高的抽象級(jí)別,并測(cè)試核心接口(ILookupList 和 ILookupCollection)的實(shí)現(xiàn)。我打算向 Web.Controls 項(xiàng)目中添加一個(gè)新類(lèi):WebLookupList 控件。圖 8 顯示此類(lèi)的第一次測(cè)試。

      某些事項(xiàng)在圖 8 所示的測(cè)試中比較突出。顯然,測(cè)試項(xiàng)目需要一個(gè)到 System.Web 庫(kù)的引用,這樣它就可以實(shí)例化 DropDownList Web 控件。進(jìn)一步查看測(cè)試,您應(yīng)了解 WebLookupList 類(lèi)將會(huì)實(shí)現(xiàn) ILookupList 接口。它還會(huì)將 ListControl 作為一個(gè)依賴(lài)對(duì)象。System.Web.UI.WebControls 命名空間中兩個(gè)最常見(jiàn)的 ListControl 實(shí)現(xiàn)是 DropDownList 和 ListBox 類(lèi)。圖 8 中測(cè)試的主要功能是要確保 WebLookupList 正確的將實(shí)際 Web ListControl 的狀態(tài)更新為其正在委派責(zé)任的狀態(tài)。圖 9 顯示 WebLookupList 實(shí)現(xiàn)中涉及的類(lèi)的類(lèi)圖表。我可以通過(guò)圖 10 中的代碼,滿(mǎn)足對(duì) WebLookupList 控件第一次測(cè)試的要求。

      a

      圖 9 WebLookupList 類(lèi)

      請(qǐng)記住,MVP 的一個(gè)關(guān)鍵是由創(chuàng)建視圖接口引入的層的分離。表示器不了解視圖的具體實(shí)現(xiàn),以及它要對(duì)話的各個(gè) ILookupList,它只知道它可以調(diào)用這些接口定義的任何方法。最后,WebLookupList 類(lèi)是一個(gè)包裝并委托至底層 ListControl 的類(lèi)(在 System.Web.UI.WebControls 項(xiàng)目中定義的某些 ListControls 的基類(lèi))。利用這些代碼,我可以編譯并運(yùn)行 WebLookupList 控件測(cè)試,現(xiàn)在測(cè)試應(yīng)該順利通過(guò)了。我可以為 WebLookupList 再添加一個(gè)測(cè)試,以測(cè)試 Clear 方法的實(shí)際行為:

      [Test]
      public void ShouldClearUnderlyingList()
      {
      ListControl webList = new DropDownList();
      ILookupList list = new WebLookupList(webList);
      webList.Items.Add(new ListItem("1", "1"));
      list.Clear();
      Assert.AreEqual(0, webList.Items.Count);
      }
      

      另外,我將測(cè)試在調(diào)用 WebLookupList 類(lèi)自身的方法時(shí),它是否會(huì)真正更改底層 ListControl (DropDownList) 的狀態(tài)。WebLookupList 現(xiàn)在可以完成填充 Web Form 中 DropDownList 的功能?,F(xiàn)在可將所有程序綁定在一起,就可獲得已填充客戶(hù)列表的 Web 頁(yè)面下拉列表。

      實(shí)現(xiàn)視圖接口

      由于我在建立 Web Form 前端,因此 IViewCustomerView 接口的實(shí)現(xiàn)程序必須是 Web Form 或用戶(hù)控件。出于此列的原因,我將其設(shè)為 Web Form。頁(yè)面的常規(guī)外觀已經(jīng)創(chuàng)建,如圖 2 所示?,F(xiàn)在我只需要實(shí)現(xiàn)視圖接口。切換到 ViewCustomers.aspx 頁(yè)的源代碼,我可以添加以下代碼,表示需要此頁(yè)來(lái)實(shí)現(xiàn) IViewCustomersView 接口:

      public partial class ViewCustomers :Page,IViewCustomerView
      

      如果觀察示例代碼,您將會(huì)發(fā)現(xiàn) Web 項(xiàng)目和 Presentation 是兩個(gè)完全不同的程序集。而且,Presentation 項(xiàng)目沒(méi)有引用任何 Web.UI 項(xiàng)目,這樣可進(jìn)一步維護(hù)分離層。另一方面,Web.UI 項(xiàng)目必須引用 Presentation 項(xiàng)目,因?yàn)橐晥D接口和表示器都位于該項(xiàng)目中。

      通過(guò)選擇實(shí)現(xiàn) IViewCustomerView 接口,現(xiàn)在我們的 Web 頁(yè)面可以實(shí)現(xiàn)由該接口定義的任何方法或?qū)傩浴.?dāng)前 IViewCustomerView 接口上只有一個(gè)屬性,是一個(gè)可返回 ILookupList 接口任何實(shí)現(xiàn)的 getter。我已向 Web.Controls 項(xiàng)目中添加了引用,這樣就可以實(shí)例化 WebLookupListControl。我這樣做是因?yàn)?WebLookupListControl 實(shí)現(xiàn)了 ILookupList 接口,并且它知道如何委托給 ASP.NET 中的實(shí)際 WebControls。請(qǐng)查看 ViewCustomer 頁(yè)面的 ASPX,您將會(huì)發(fā)現(xiàn)客戶(hù)列表只是一個(gè) asp:DropDownList 控件:

      <td>Customers:</td>
      <td><asp:DropDownList id="customerDropDownList" AutoPostBack="true"
      runat="server" Width="308px"></asp:DropDownList></td>
      </tr>
      

      利用這些已有代碼,我可以快速的繼續(xù)實(shí)現(xiàn)滿(mǎn)足 IViewCustomerView 接口實(shí)現(xiàn)所需的代碼:

      public ILookupList CustomerList
      {
      get { return new WebLookupList(this.customerDropDownList);}
      }
      

      我現(xiàn)在需要調(diào)用表示器上的 Initialize 方法,以觸發(fā)該方法實(shí)際執(zhí)行一些操作。因此,視圖需要能夠?qū)嵗硎酒?,這樣就可以調(diào)用它的方法了。如果回頭查看一下表示器,您會(huì)記得它需要視圖和服務(wù)與之配合使用。ICustomerTask 接口表示位于應(yīng)用程序服務(wù)層的接口。服務(wù)層通常負(fù)責(zé)協(xié)調(diào)域?qū)ο笾g的交互,并將這些交互的結(jié)果轉(zhuǎn)換為“數(shù)據(jù)傳輸對(duì)象”(Data Transfer Objects, DTO),然后將其從服務(wù)層傳遞到表示層,再到 UI 層。但是此處有一個(gè)問(wèn)題:我已規(guī)定表示器需要與視圖和服務(wù)實(shí)現(xiàn)一同構(gòu)造。

      表示器的實(shí)際實(shí)例化將在 Web 頁(yè)的源代碼中進(jìn)行。這是一個(gè)問(wèn)題,因?yàn)?UI 項(xiàng)目沒(méi)有引用任何服務(wù)層項(xiàng)目。但是,表示項(xiàng)目卻引用了服務(wù)層項(xiàng)目。通過(guò)將一個(gè)重載構(gòu)造函數(shù)添加到 ViewCustomerPresenterClass 中,可以解決此問(wèn)題:

      public ViewCustomerPresenter(IViewCustomerView view) :
      this(view, new CustomerTask()) {}
      

      這一新的構(gòu)造函數(shù)同時(shí)滿(mǎn)足了表示器視圖和服務(wù)的實(shí)現(xiàn)要求,同時(shí)還可從服務(wù)層維護(hù) UI 層的分離?,F(xiàn)在完成源代碼的后續(xù)代碼就很簡(jiǎn)單了:

      protected override void OnInit(EventArgs e)
      {
      base.OnInit(e);
      presenter = new ViewCustomerPresenter(this);
      }
      protected void Page_Load(object sender, EventArgs e)
      {
      if (!IsPostBack) presenter.Initialize();
      }
      

      請(qǐng)注意,表示器實(shí)例化的關(guān)鍵是:我將利用新建的構(gòu)造函數(shù)重載,并且 Web Form 會(huì)將其自身作為實(shí)現(xiàn)視圖接口的對(duì)象傳入。

      利用實(shí)現(xiàn)的源代碼中的代碼,我可以立即創(chuàng)建并運(yùn)行應(yīng)用程序。現(xiàn)在不需要源代碼中的任何數(shù)據(jù)綁定代碼,就可以使用客戶(hù)名稱(chēng)列表來(lái)填充 Web 頁(yè)上的 DropDownList。另外,已在最終一起工作的所有代碼段上運(yùn)行了測(cè)試分?jǐn)?shù),這可確保表示層體系結(jié)構(gòu)將按預(yù)期運(yùn)轉(zhuǎn)。

      現(xiàn)在我準(zhǔn)備展示一下在 DropDownList 中顯示選定客戶(hù)信息所需的步驟,以此來(lái)總結(jié)我對(duì) MVP 的討論。再次重申,我將首先編寫(xiě)一個(gè)測(cè)試,來(lái)描述我所希望觀察到的行為。(請(qǐng)參閱圖 11)。

      如上所述,我將利用 NMock 程序庫(kù)來(lái)創(chuàng)建任務(wù)和視圖接口的模擬。此特定測(cè)試將通過(guò)向服務(wù)層請(qǐng)求表示特定客戶(hù)的 DTO 來(lái)驗(yàn)證表示器的行為。表示器從服務(wù)層檢索到 DTO 后,它將直接更新視圖上的屬性,這樣視圖就不必了解任何有關(guān)如何正確顯示對(duì)象信息的知識(shí)。簡(jiǎn)便起見(jiàn),我將不再討論 WebLookupList 控件上 SelectedItem 屬性的實(shí)現(xiàn);相反,我會(huì)將它留給您去檢查源代碼,以了解實(shí)現(xiàn)的詳細(xì)信息。此測(cè)試真正展示的是在表示器從服務(wù)層檢索 CustomerDTO 后,表示器和視圖之間發(fā)生的交互。如果現(xiàn)在嘗試運(yùn)行測(cè)試,我將面臨一個(gè)嚴(yán)重的失敗,因?yàn)橐晥D接口上的許多屬性都還不存在。因此,我將繼續(xù)進(jìn)行并為 IViewCustomerView 接口添加必要的成員,如圖 12 所示。

      這些接口成員添加完成之后,我的 Web Form 也許會(huì)抱怨,因?yàn)樗辉贊M(mǎn)足接口協(xié)議了,所以我必須返回 Web Form 的源代碼并實(shí)現(xiàn)其余的成員。如上所述,Web 頁(yè)的整個(gè)標(biāo)記已經(jīng)創(chuàng)建,同時(shí)表格單元格已被標(biāo)記為 "runat=server" 屬性,并且已根據(jù)其應(yīng)顯示的信息進(jìn)行了命名。這樣就可以使結(jié)果代碼非常輕松的實(shí)現(xiàn)接口成員:

      public string CompanyName
      {
      set { this.companyNameLabel.InnerText = value; }
      }
      public string ContactName
      {
      set { this.contactNameLabel.InnerText = value; }
      }
      ...
      

      隨著 setter 屬性的實(shí)現(xiàn),現(xiàn)在只剩下最后一件事要完成。我需要一種方法來(lái)告訴表示器顯示選定客戶(hù)的信息?;仡^看看測(cè)試,您會(huì)發(fā)現(xiàn)此行為的實(shí)現(xiàn)位于表示器的 DisplayCustomerDetails 方法中。但是,此方法不帶有任何參數(shù)。調(diào)用時(shí),表示器將返回視圖,從中提取其所需的任何信息(使用 ILookupList 檢索),然后使用該信息檢索選定客戶(hù)的詳細(xì)信息。從 UI 角度看,我需要做的就是將 DropDownList 的 AutoPostBack 屬性設(shè)置為 true,我還需要將以下事件處理程序掛鉤代碼添加到頁(yè)面的 OnInit 方法中:

      protected override void OnInit(EventArgs e)
      {
      base.OnInit(e);
      presenter = new ViewCustomerPresenter(this);
      this.customerDropDownList.SelectedIndexChanged += delegate
      {
      presenter.DisplayCustomerDetails();
      };
      }
      

      此事件處理程序可確保在下拉列表中選擇新客戶(hù)時(shí),視圖將請(qǐng)求表示器顯示該客戶(hù)的詳細(xì)信息。

      重要的是注意這是典型行為。當(dāng)視圖請(qǐng)求表示器執(zhí)行操作時(shí),它不會(huì)給予任何特定的詳細(xì)信息,并且將由表示器來(lái)決定是否返回視圖,并使用視圖接口來(lái)獲取其所需的任何信息。圖 13 顯示實(shí)現(xiàn)表示器中所需行為的代碼。

      希望您現(xiàn)在可以了解添加表示器層的價(jià)值了。表示器負(fù)責(zé)嘗試檢索需要顯示其詳細(xì)信息的客戶(hù) ID。這就是通常在源代碼中執(zhí)行的代碼,但是它現(xiàn)在位于類(lèi)中,我可以在任何表示層技術(shù)以外對(duì)其進(jìn)行完全的測(cè)試和實(shí)踐。

      如果表示器能夠從視圖中檢索有效的客戶(hù) ID,則它將轉(zhuǎn)向服務(wù)層并請(qǐng)求表示該客戶(hù)詳細(xì)信息的 DTO。表示器獲得 DTO 后,它將使用 DTO 中包含的信息更新視圖。要注意的關(guān)鍵一點(diǎn)是視圖接口的簡(jiǎn)單性,除 ILookupList 接口以外,視圖接口完全由字符串 DataTypes 組成。表示器的最終職責(zé)是正確地轉(zhuǎn)換和格式化從 DTO 中檢索的信息,這樣它就可以作為字符串,實(shí)際被傳遞到視圖。雖然未在此例中說(shuō)明,但表示器還可負(fù)責(zé)從視圖中讀取信息,并將其轉(zhuǎn)換為服務(wù)層所期待的必要類(lèi)型。

      完成所有代碼段后,我現(xiàn)在就可以運(yùn)行應(yīng)用程序了。首次加載頁(yè)面時(shí),我會(huì)獲得一個(gè)客戶(hù)列表,并且在 DropDownList 中顯示(未選中)第一個(gè)客戶(hù)。如果我選擇一個(gè)客戶(hù),則會(huì)出現(xiàn)回發(fā),視圖與表示器之間發(fā)生交互,并且會(huì)使用相關(guān)的客戶(hù)信息更新 Web 頁(yè)面。

      未來(lái)計(jì)劃

      Model View Presenter 設(shè)計(jì)模式實(shí)際上就是許多開(kāi)發(fā)人員已經(jīng)熟悉的模板視圖控制器的一個(gè)最新版本;兩者的主要區(qū)別是 MVP 真正將 UI 從應(yīng)用程序的域/服務(wù)層中分離。雖然從需求角度看,此示例十分簡(jiǎn)單,但它可以幫助您抽象化 UI 與應(yīng)用程序其他層之間的交互。而且,現(xiàn)在您可了解多種方法:您可間接使用這些層來(lái)自動(dòng)測(cè)試您的應(yīng)用程序。隨著您對(duì) MVP 模式的深入研究,我希望您可以找到其他方法,從源代碼中提取更多格式和條件邏輯,并將其置入可測(cè)試視圖/表示器交互模型中。

      請(qǐng)將您的問(wèn)題和意見(jiàn)發(fā)送至 mmpatt@microsoft.com。

      Jean-Paul Boodhoo 是 ThoughtWorks 的一名高級(jí) .NET 交付專(zhuān)家,他曾參與了許多使用 .NET 框架和各種靈活方法的企業(yè)級(jí)應(yīng)用程序交付。他經(jīng)常利用測(cè)試驅(qū)動(dòng)開(kāi)發(fā)提供有關(guān)使用 .NET 功能的演示??赏ㄟ^(guò) mailtio:bitwisejp@gmail.comwww./blog 聯(lián)系 Jean-Paul。

      本文摘自 MSDN Magazine2006 年 8 月號(hào)。
       
      Figure 4 The First Test
      [Test]
      public void ShouldLoadListOfCustomersOnInitialize()
      {
      mockery = new Mockery();
      ICustomerTask  mockCustomerTask = mockery.NewMock<ICustomerTask>();
      IViewCustomerView  mockViewCustomerView =
      mockery.NewMock<IViewCustomerView>();
      ILookupList  mockCustomerLookupList = mockery.NewMock<ILookupList>();
      ViewCustomerPresenter presenter =
      new ViewCustomerPresenter(mockViewCustomerView,
      mockCustomerTask);
      ILookupCollection mockLookupCollection =
      mockery.NewMock<ILookupCollection>();
      Expect.Once.On(mockCustomerTask).Method(
      "GetCustomerList").Will(Return.Value(mockLookupCollection));
      Expect.Once.On(mockViewCustomerView).GetProperty(
      "CustomerList").Will(Return.Value(mockCustomerLookupList));
      Expect.Once.On(mockLookupCollection).Method(
      "BindTo").With(mockCustomerLookupList);
      presenter.Initialize();
      }
      

      Figure 5 Compiling the Test
      public class ViewCustomerPresenter
      {
      private readonly IViewCustomerView view;
      private readonly ICustomerTask task;
      public ViewCustomerPresenter(
      IViewCustomerView view, ICustomerTask task)
      {
      this.view = view;
      this.task = task;
      }
      public void Initialize()
      {
      throw new NotImplementedException();
      }
      }
      

      Figure 6 The LookupCollection Class
      public class LookupCollection : ILookupCollection
      {
      private IList<ILookupDTO> items;
      public LookupCollection(IEnumerable<ILookupDTO> items)
      {
      this.items = new List<ILookupDTO>(items);
      }
      public int Count { get { return items.Count; } }
      public void BindTo(ILookupList list)
      {
      list.Clear();
      foreach (ILookupDTO dto in items) list.Add(dto);
      }
      }
      

      Figure 7 A Test that Describes Behavior
      [Test]
      public void ShouldBeAbleToBindToLookupList()
      {
      IList<ILookupDTO> dtos = new IList;
      ILookupList mockLookupList = mockery.NewMock<ILookupList>();
      Expect.Once.On(mockLookupList).Method("Clear");
      for (int i = 0; i < 10; i++)
      {
      SimpleLookupDTO dto =
      new SimpleLookupDTO(i.ToString(),i.ToString());
      dtos.Add(dto);
      Expect.Once.On(mockLookupList).Method("Add").With(dto);
      }
      new LookupCollection(dtos).BindTo(mockLookupList);
      }
      

      Figure 8 First Test for WebLookupList Control
      [Test]
      public void ShouldAddItemToUnderlyingList()
      {
      ListControl webList = new DropDownList();
      ILookupList list = new WebLookupList(webList);
      SimpleLookupDTO dto = new SimpleLookupDTO("1","1");
      list.Add(dto);
      Assert.AreEqual(1, webList.Items.Count);
      Assert.AreEqual(dto.Value, webList.Items[0].Value);
      Assert.AreEqual(dto.Text, webList.Items[0].Text);
      }
      

      Figure 10 WebLookupList Control
      public class WebLookupList : ILookupList
      {
      private ListControl underlyingList;
      public WebLookupList(ListControl underlyingList)
      {
      this.underlyingList = underlyingList;
      }
      public void Add(ILookupDTO dto)
      {
      underlyingList.Items.Add(new ListItem(dto.Text, dto.Value));
      }
      }
      

      Figure 11 One Last Test
      [Test]
      public void ShouldDisplayCustomerDetails()
      {
      SimpleLookupDTO lookupDTO = new SimpleLookupDTO("1","JPBOO");
      CustomerDTO dto = new CustomerDTO("BLAH", "BLAHCOMPNAME",
      "BLAHCONTACTNAME", "BLAHCONTACTTILE", "ADDRESS", "CITY",
      "REGION", "POSTALCODE", Country.CANADA, "4444444", "4444444");
      Expect.Once.On(mockViewCustomerView).GetProperty(
      "CustomerList").Will(Return.Value(mockCustomerLookupList));
      Expect.Once.On(mockCustomerLookupList).GetProperty(
      "SelectedItem").Will(Return.Value(lookupDTO));
      Expect.Once.On(mockCustomerTask).Method(
      "GetDetailsForCustomer").With(1).Will(Return.Value(dto));
      Expect.Once.On(mockViewCustomerView).SetProperty(
      "CompanyName").To(dto.CompanyName);
      Expect.Once.On(mockViewCustomerView).SetProperty(
      "ContactName").To(dto.ContactName);
      Expect.Once.On(mockViewCustomerView).SetProperty(
      "ContactTitle").To(dto.ContactTitle);
      Expect.Once.On(mockViewCustomerView).SetProperty(
      "Address").To(dto.Address);
      Expect.Once.On(mockViewCustomerView).SetProperty(
      "City").To(dto.City);
      Expect.Once.On(mockViewCustomerView).SetProperty(
      "Region").To(dto.Region);
      Expect.Once.On(mockViewCustomerView).SetProperty(
      "PostalCode").To(dto.PostalCode);
      Expect.Once.On(mockViewCustomerView).SetProperty(
      "Country").To(dto.CountryOfResidence.Name);
      Expect.Once.On(mockViewCustomerView).SetProperty(
      "Phone").To(dto.Phone);
      Expect.Once.On(mockViewCustomerView).SetProperty("Fax").To(dto.Fax);
      presenter.DisplayCustomerDetails();
      }
      

      Figure 12 Completing the IVewCustomerView Interface
      public interface IViewCustomerView
      {
      ILookupList CustomerList{get;}
      string CompanyName{set;}
      string ContactName{set;}
      string ContactTitle{set;}
      string Address{set;}
      string City{set;}
      string Region{set;}
      string PostalCode{set;}
      string Country{set;}
      string Phone{set;}
      string Fax{set;}
      }
      

      Figure 13 Completing the Presenter
      public void DisplayCustomerDetails()
      {
      int? customerId = SelectedCustomerId;
      if (customerId.HasValue)
      {
      CustomerDTO customer =
      task.GetDetailsForCustomer(customerId.Value);
      UpdateViewFrom(customer);
      }
      }
      private int? SelectedCustomerId
      {
      get
      {
      string selectedId = view.CustomerList.SelectedItem.Value;
      if (String.IsNullOrEmpty(selectedId)) return null;
      int? id = null;
      try
      {
      id = int.Parse(selectedId.Trim());
      }
      catch (FormatException) {}
      return id;
      }
      }
      private void UpdateViewFrom(CustomerDTO customer)
      {
      view.CompanyName = customer.CompanyName;
      view.ContactName = customer.ContactName;
      view.ContactTitle = customer.ContactTitle;
      view.Address = customer.Address;
      view.City = customer.City;
      view.Region = customer.Region;
      view.Country = customer.CountryOfResidence.Name;
      view.Phone = customer.Phone;
      view.Fax = customer.Fax;
      view.PostalCode = customer.PostalCode;
      }
      

        本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶(hù)發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評(píng)論

        發(fā)表

        請(qǐng)遵守用戶(hù) 評(píng)論公約

        類(lèi)似文章 更多