亚洲免费在线-亚洲免费在线播放-亚洲免费在线观看-亚洲免费在线观看视频-亚洲免费在线看-亚洲免费在线视频

模型-視圖-提供器 模式

系統(tǒng) 1884 0

模型-視圖-提供器 模式

出處:
1. http://www.tracefact.net/Software-design/Model-View-Presenter-Pattern.aspx
2. http://msdn.microsoft.com/en-us/magazine/cc188690.aspx

引言

隨著像Asp.Net和Windows窗體這樣的用戶界面創(chuàng)建技術越來越強大,讓用戶界面層做多于它本應做的事是很常見的。沒有一個清晰的職責劃分,UI層經(jīng)常淪為一個包含實際上應屬于程序其他層的邏輯的容器。有一個稱為 模型(Model)-視圖(View)-提供器(Presenter)(MVP)的設計模式,特別適合解決這個問題。為了表明我的觀點,我將為Northwind數(shù)據(jù)庫中的客戶建一個遵循MVP模式的顯示屏幕(display screen)。

為什么在UI層包含太多的邏輯是很糟糕的?在既不手動運行應用程序,也不維護丑陋的自動執(zhí)行UI組件的UI運行者腳本(runner script)的情況下,位于應用程序UI層中的代碼是非常難于調(diào)試的。雖然這本身就是一個很大的問題,一個更大的問題是在應用程序的公共視圖之間會有大量的重復代碼。當執(zhí)行某一特定業(yè)務的功能在UI層的不同部分之間拷貝,通常很難找到好的可選重構(gòu)方法。MVP設計模式使得將UI層中的邏輯和代碼 重構(gòu)為 更加易于測試的新型的、可重用的代碼 更加容易。

圖1 演示了組成一個范例應用程序的主要層。注意對于UI和表現(xiàn)(Pesentation)有著各自的包(Package)。你可能會想它們是一樣的,但是實際上項目中的UI層應該只包含各種不同的UI元素――窗體和控件。典型地,在一個Web窗體項目中是Asp.Net Web窗體、用戶控件、服務器控件的集合;在Windows項目中,它是Windows 窗體、用戶控件以及第三方庫(Libraries)的集合。這一額外的層就是將顯示和邏輯分隔開的層。在表現(xiàn)層,你擁有實際上實現(xiàn)UI行為的對象――諸如驗證顯示,從UI層收集用戶輸入 等等。

模型-視圖-提供器 模式

圖1.應用程序構(gòu)架

遵循MVP

如同你在 圖2 中所見的,這個項目的UI相當標準。當頁面加載時,屏幕將會顯示一個包含Northwind數(shù)據(jù)庫中所有客戶的下拉框。如果你從下拉框中選擇一個客戶,頁面會更新為這個客戶的信息。通過遵循MVP設計模式,你可以從UI中將行為(Behavior)重構(gòu)到它們自己的類中。 圖3 顯示了一個類圖,它說明了參與其中的各個不同類之間的聯(lián)系。

模型-視圖-提供器 模式

圖2. 用戶信息

模型-視圖-提供器 模式

圖3. MVP類圖

注意到提供器對于應用程序?qū)嶋H的UI層一無所知非常重要。它知道它可以同接口對話,但是它不知道也不關心接口的實現(xiàn)是什么。這提升了在完全不同的UI技術間提供器的重用。

我將使用測試驅(qū)動開發(fā)(TDD)創(chuàng)建客戶界面的功能。 代碼4 演示了第一次測試的細節(jié),我將通過這個測試來描述我期望在頁面加載時觀察到的行為。TDD讓我每次關注于一個問題,僅編寫可以讓測試通過的代碼,然后繼續(xù)進行下面的工作。在測試中,我將會利用一個稱為NMork2 的偽對象框架,它允許我創(chuàng)建接口的偽實現(xiàn)(mock implementation)。

代碼4.第一個測試

[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();
}

在我的MVP實現(xiàn)中,我決定提供器將作為視圖所要與之工作的依賴。通常,創(chuàng)建對象使之處于可以立刻進行工作的狀態(tài)是一種好的做法。在這個應用程序中,表現(xiàn)層依賴于服務層,實際上由服務層調(diào)用領域功能(domain functionality)。因為這個需求,創(chuàng)建一個含有可以與服務類對話的接口的提供器也是有意義的。這樣確保了一旦提供器創(chuàng)建好了,它就已經(jīng)準備好做它需要做的所有工作了。我以創(chuàng)建兩個特定的mocks作為開始:一個用于服務層,一個用于提供器將與協(xié)作的視圖。

為什么使用mocks?單元測試的一個規(guī)則就是盡可能地隔離測試以便集中于某一特定的對象。在這個測試中,我只關心提供器所期待的行為。目前我并不關心view接口或者service接口的實際實現(xiàn)。我信任由這些接口定義的契約(contract),并且設置mocks去相應運作(behave)。這樣確保了我的測試僅僅圍繞著我對提供器所期望的行為,而不是它所依賴的任何東西。我期望的,在提供器的初始化方法被調(diào)用后所表現(xiàn)的行為如下:

首先,提供器應該調(diào)用一次服務層ICustomerTask對象(已經(jīng)在測試中Mock了)的GetCustomerList方法。注意通過使用NMock,我可以模擬Mock的行為。以服務層來說,我想要返回一個ILookupCollection給提供器。然后,在提供器從服務層收到ILookupCollection以后,它可以調(diào)用集合的BindTo方法 并且向方法傳遞一個ILookupList方法的實現(xiàn)。通過使用NMockExpect。一旦我可以確定方法,如果提供器沒有調(diào)用這個方法一次并且只一次,那么測試將會失敗。

寫完測試以后,我處于一個完全不可編譯的狀態(tài)。我將要做一些可能的最簡單的事讓測試通過。

讓第一個測試通過

先寫一個測試的好處之一是我現(xiàn)在有了一個我可以遵循的使得測試編譯并最終通過的藍圖(這個測試)。第一個測試還有兩個尚不存在的接口。這些接口是代碼正確通過編譯的第一個先決條件。我們將以IViewCustomerView的代碼作為開始:

public interface IViewCustomerView {
??? ILookupList CustomerList { get; }
}

這個接口暴露一個返回ILookupList接口實現(xiàn)的屬性。我還沒有ILookupList接口或是它的一個實現(xiàn),就此而言。出于使測試通過的目的,我不需要一個顯示的實現(xiàn),所以我可以這樣去創(chuàng)建ILookupList接口:

public interface ILookupList { }

ILookupList接口現(xiàn)在看上去相當?shù)臎]用。我的目標是使得測試編譯并且通過,并且這些接口滿足測試的需要。現(xiàn)在是時候?qū)⒔裹c轉(zhuǎn)移到我們實際要進行測試的對象上了――ViewCustomerPresenter。這個類現(xiàn)在還不存在,但是看下測試,你可以發(fā)現(xiàn)關于它的兩個要點:它有一個既需要視圖實現(xiàn)也需要服務實現(xiàn)作為依賴的構(gòu)造函數(shù),并且它有一個無返回值的初始化方法。 代碼5 演示了如何使測試通過編譯:

代碼5. 編譯這個測試

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();
??? }
}

應該記得,為了讓提供器有意義地工作,它需要獲得它的所有依賴;這就是為什么傳遞視圖和服務進去。我沒有實現(xiàn)初始化方法,所以如果我運行這個測試我會得到一個NotImplementedException 異常。

如果我已經(jīng)提到的,我不會盲目地對提供器進行編碼;我已經(jīng)知道,通過觀察這個測試,在初始化方法被調(diào)用時,提供器應該顯示出什么樣的行為。這個行為的實現(xiàn)如下所示:

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

在這篇文章所附帶的源代碼中,在CustomerTask類(它實現(xiàn)了ICustomerTask接口)中有GetCustomerList方法的完整實現(xiàn)。然而,從實現(xiàn)和測試提供器的角度來說,我不需要知道是否有一個可以工作的實現(xiàn)。正是這種級別的抽象允許我在提供器類的測試中穿行。第一個測試現(xiàn)在處于可以編譯并運行的狀態(tài)。這證明了當提供器的初始化方法被調(diào)用,它將會以一種我在測試中所指定的方式與它所依賴的類型進行交互,并且最終,當這些依賴的具體實現(xiàn)注入到提供器中,我可以確定結(jié)果視圖(ASPX頁面)將會由客戶列表所填充。

填充 DropDownList

迄今為止,我主要在處理接口以便將實際的實現(xiàn)細節(jié)抽象出來、將注意力集中在提供器上。現(xiàn)在是時候通過一種可測試的方式創(chuàng)建一些底層代碼(plumbing),這些底層代碼將最終允許提供器在Web頁面上填充一個列表。完成這個工作的關鍵是將發(fā)生在LookupCollection類的BindTo方法中的交互。如果你看下 代碼6 中LookupCollection類的實現(xiàn),你將注意到它實現(xiàn)了IlookupCollection接口。這篇文章的源碼含有附帶的測試,用于創(chuàng)建LookupCollection類的功能。

代碼6. LookupCollection類

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);
??? }
}

BindTo方法的實現(xiàn)值得特別注意。注意到在這個方法中,集合遍歷了它自己的私有ILookupDTO 列表的實現(xiàn)。ILookupDTO是一個接口,它迎合了UI層的綁定下拉框。

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

代碼7 演示了測試lookup集合的BindTo方法的代碼,這有助于解釋LookupCollection和IlookupList之間所期望的交互。最后一行值得特別注意。在這個測試中,我期望在試圖添加項目到列表之前,LookupCollection將會調(diào)用IlookupList實現(xiàn)的Clear方法。然后我期望Add方法在IlookupList上調(diào)用10次,并且LookupCollection將傳遞一個實現(xiàn)了ILookupDTO接口的對象,作為Add方法的一個參數(shù)。為了能夠?qū)嶋H工作在一個Web項目中的控件上(比如一個下拉列表),你將需要創(chuàng)建一個IlookupList的實現(xiàn),它知道如何與Web項目中的控件工作。

代碼7 一個描述行為的測試

[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);
}

這篇文章附帶的源碼中包含一個名為MVP.Web.Controls的項目。這個項目包含了我選擇創(chuàng)建的用于完成解決方案的任何基于Web的控件或者類。為什么我要把代碼放到這個項目中,而沒有放在App_Code目錄或者Web項目本身中?易測性。在沒有手動運行應用程序或者使用某種類型的測試機器人自動操作UI的情況下,居于Web項目中的任何東西都是難于直接測試的。MVP模式允許我在一個較高的層次上考慮抽象,并且測試核心接口(IlookupList和ILookupCollection)的實現(xiàn),而不需要手動地運行程序。我將在Web.Controls項目中添加一個新類,一個WebLookupList控件。 代碼8 演示了這個類的第一次測試:

代碼8. WebLookupList 控件的第一次測試

[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);
}

測試中關鍵的部分在 代碼8 中顯示了。這個測試項目顯然需要System.Web庫的一個引用,以便它可以初始化DropDownList Web控件。看下這個測試,你應該看到WebLookupList類將會實現(xiàn)IlookupList接口。它也將把ListControl作為一個依賴。在System.Web.UI.WebControls命名空間中的兩個最常見的ListControl的實現(xiàn)就是DropDownList和ListBox類了。 代碼8 中的一個關鍵特色就是我確信WebLookupList正確的更新了Web ListControl的狀態(tài),它將職責委托給了這個Web ListControl。 圖9 顯示了參與WebLookupList實現(xiàn)的類的類圖。通過 代碼10 ,我可以滿足WebLookupList控件第一次測試的需求。

代碼10 WebLookupList 控件

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));
??? }
}

模型-視圖-提供器 模式

圖9. WebLookupList 類

記得,MVP模式的要點之一是通過view接口的創(chuàng)建引入了各層之間的分離。提供器不知道某一視圖的具體實現(xiàn),以及它所要交互的IlookupList;它只知道它可以調(diào)用由這些接口所定義的任何方法。最終,WebLookupList是一個包裝了ListControl并且將職責委托給了ListControl(一些定義在System.Web.UI.WebControls項目中的ListControls的基類)的類。在這些代碼完成好了以后,我現(xiàn)在可以編譯并運行WebLookupList控件的測試了,它應該可以通過。我可以為WebLookupList控件再添加一個測試來測試Clear方法的實際行為。

[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);
}

我再次測試到,當WebLookupList類本身的方法被調(diào)用時,實際上會改變它底層的ListControl(DropDownList)的狀態(tài)。WebLookupList現(xiàn)在完全擁有完成填充Web表單上一個DropDownList的特色了。現(xiàn)在是時候讓我把所有東西都結(jié)合到一起,然后讓客戶列表填充這個Web頁面的下拉框了。

實現(xiàn)View接口

因為我正在創(chuàng)建一個Web窗體前端(界面),將IViewCustomerView接口實現(xiàn)為一個Web窗體或者用戶控件將是有意義的。出于這個專欄的目的,我將創(chuàng)建一個Web窗體。如同你在 圖2 中所見到的,這個頁面大概的樣子已經(jīng)創(chuàng)建好了。現(xiàn)在我只需要實現(xiàn)View接口。切換到ViewCustomers.aspx頁面的后置代碼中,我可以添加下面的代碼,表示這個頁面需要實現(xiàn)IViewCustomersView接口:

public partial class ViewCustomers : Page,IViewCustomerView

如果你看一下代碼范例,你將會注意到Web項目和表現(xiàn)(Presentation)是兩個完全不同的程序集。同樣,表現(xiàn)項目沒有包含對Web.UI項目的任何引用,進一步維持著層的分隔。另一方面,Web.UI項目必須包含一個對表現(xiàn)項目的一個引用,因為它包含了View接口和提供器。

通過選擇實現(xiàn)IViewCustomerView接口,我們的Web頁面現(xiàn)在需要實現(xiàn)由那個接口所定義的任何方法和屬性。現(xiàn)在IViewCustomerView接口只有一個屬性,這是一個返回任何實現(xiàn)了ILookupList接口的只讀屬性。我添加了一個對Web.Controls項目的引用,以便我可以初始化WebLookupListControl。這樣做是因為WebLookupListControl實現(xiàn)了ILookupList接口,并且它知道如何(將工作)委托給實際的Asp.Net中的WebControls。看一下ViewCustomer頁面的Aspx文件,你將會看到客戶列表僅僅是一個簡單的asp:DropDownList控件:

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

這些已經(jīng)就位了,我們可以立刻繼續(xù)去實現(xiàn)滿足IViewCustomerView接口實現(xiàn)的代碼了:

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

我現(xiàn)在需要在提供器上調(diào)用初始化方法,這個方法將觸發(fā)它去做些實際的工作。為了完成這個,視圖需要能夠初始化提供器,以便它的方法可以被調(diào)用。如果你回頭看下提供器,你將會記得它需要與視圖和服務工作。ICustomerTask代表一個居于應用程序服務層中的一個接口。典型地服務層負責監(jiān)管領域?qū)ο箝g的交互,以及將這些交互的結(jié)果轉(zhuǎn)換成數(shù)據(jù)傳遞對象(DTOs),然后這些DTO對象從服務層傳遞到表現(xiàn)層,接著傳遞到UI層。然而,這里有一個問題,我規(guī)定提供器需要視圖和服務的實現(xiàn)才能創(chuàng)建。

提供器實際的初始化將發(fā)生在Web頁面的后置代碼中。這是一個問題,因為UI項目不包含對服務層項目的引用。然而,表現(xiàn)層項目包含,它有一個對服務層項目的引用。這允許我通過在ViewCustomverPresenter中添加一個重載的構(gòu)造函數(shù)來解決這個問題:

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

新的構(gòu)造函數(shù)滿足了提供器的需求:同時擁有視圖和服務的實現(xiàn),并且保持將UI層從服務層中分離出來。現(xià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();
}

注意到初始化提供器的關鍵是:我利用了我新創(chuàng)建的重載構(gòu)造函數(shù),并且Web窗體將它本身作為一個實現(xiàn)了View接口的對象進行傳遞!

后置代碼已經(jīng)實現(xiàn),現(xiàn)在我可以生成并運行應用程序了。Web頁面上的DropDownList現(xiàn)在填充了客戶名稱列表,而在后置代碼中不需要任何的數(shù)據(jù)綁定代碼。不僅如此,曾經(jīng)運行的各個小部分的測試最終協(xié)同工作了,確保了表現(xiàn)層構(gòu)架將會如期望般運作。

我將通過演示顯示一個在DropDownList中選中的客戶信息,把我關于MVP的討論聯(lián)系起來。再一次,我通過寫一個描述了我希望觀察到的行為的測試作為開始(看 代碼11 )。

代碼11. 最后一個測試

[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();
}

和前面一樣,我利用NMock庫創(chuàng)建task和View接口的Mocks。這個特定的測試通過向服務層請求一個代表某一特定客戶的DTO,驗證了提供器的行為。一旦提供器從服務層獲得DTO,它將直接更新視圖的屬性,這就避免了視圖需要知道如何正確地顯示來自對象的信息。為了簡潔,我不打算去討論WebLookupList控件的SeletedItem屬性的實現(xiàn);然而,我將把它留給你,通過檢查源代碼來查看實現(xiàn)的細節(jié)。這個測試真正演示的是當提供器從服務層收到一個Customer DTO 時發(fā)生在提供器和視圖之間的交互。如果我現(xiàn)在試圖運行這個測試,我將會處于一個嚴重的錯誤狀態(tài),因為很多的屬性view接口沒有定義。所以我將繼續(xù)為IViewCustomerView接口添加必要的成員,如同你在 代碼12 看到的:

代碼12. 完成 IViewCustomerView 接口

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

剛添加完這些接口成員,我的Web窗體就開始抱怨了,因為它不再滿足接口的定義,所以我不得不回頭看下我的Web窗體的后置代碼,并且實現(xiàn)那些剩下的成員。如同前面所陳述的,Web頁面的整個標記都已經(jīng)創(chuàng)建了,并讓那些標記了“runat=server”的表格單元格根據(jù)將在它中所要顯示的信息來為它命名。這將使實現(xiàn)接口成員的代碼非常的輕易:

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

實現(xiàn)了Set屬性訪問器,還剩下一件事需要做。我需要有一種方式通知提供器,以便顯示選中客戶的信息。回頭看下測試,你可以看到這個行為的實現(xiàn)位于提供器的DisplayCustomerDetails方法上。然而,這個方法不會接受任何參數(shù)。當調(diào)用時,提供器將會回頭找視圖,從它中拖出任何所需要的信息(它通過使用ILookupList獲取),然后使用這些信息獲取所請求的客戶的詳細內(nèi)容。從UI的角度來看,我需要做的全部就是將DropDownList的AutoPostBack屬性設為True,我也需要添加下面的事件處理程序,和Page的OnInit方法掛接起來。

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

這個事件處理程序確保,無論什么時候下拉框中的一個新的客戶被選中,視圖將會請求提供器顯示客戶的細節(jié)。

注意到這是一個典型的行為很重要。當一個視圖請求提供器做一些事情,它不提供任何的特定細節(jié),而是由提供器去訪問視圖,通過view接口獲取它所需要的任何信息。 代碼13 顯示了實現(xiàn)提供器的行為所需要的代碼。

代碼13 完成提供器

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

希望你現(xiàn)在已經(jīng)明白了添加提供器層的價值。試圖獲取一個客戶Id并且顯示它的詳細信息都是提供器的責任。這段代碼通常都是實現(xiàn)在后置代碼中,但是現(xiàn)在它位于一個類中,這樣我就可以在任何的表現(xiàn)層技術之外,對它進行完全的測試和演練( 譯注: 同一段代碼可以應用于WinForm和WebForm,讓窗體都去實現(xiàn)view接口就可以了)。

在提供器從視圖中獲得一個正確的客戶Id的事件中,它轉(zhuǎn)向服務層并請求一個DTO,這個DTO代表了客戶的細節(jié)。一旦提供器擁有了DTO,它使用包含在DTO中的信息更新視圖。注意到一個關鍵點就是View接口很簡潔;伴于ILookupList接口,view接口只包含了String 類型。正確地轉(zhuǎn)換并且格式化由DTO獲取的信息,以便它可以以字符串形式提交給視圖,最終都是提供器的責任。雖然在范例中沒有演示,提供器也應該負責從視圖中讀取信息,并且將它轉(zhuǎn)變?yōu)榉諏铀谕谋匾愋汀?

所有的小部分都已經(jīng)就位,現(xiàn)在我可以運行應用程序了。當頁面第一次加載,我獲取了客戶的一個列表,并且第一個客戶(未選擇)顯示在DropDownList中。如果我選擇一個客戶,產(chǎn)生一個PostBack,視圖和提供器發(fā)生交互,使用相關的客戶信息更新頁面。

接下來是什么?

模型-視圖-提供器模式實際上僅僅是許多開發(fā)者已經(jīng)熟悉的 模型-視圖-控制器 模式的更新。關鍵的變化是MVP完全將UI從應用程序的 領域/服務層分離出來。盡管從需求角度來看,這個例子相當?shù)暮唵危梢詭椭銖哪愕膽贸绦蛑袑I層與其他層的交互抽象出來。當你深入鉆研到MVP模式中,我希望你可以找到其他方法將盡可能多的格式化和條件判斷邏輯從你的后置代碼中分離出來,并將它們置于可測試的 視圖/提供器 交互模型中

模型-視圖-提供器 模式


更多文章、技術交流、商務合作、聯(lián)系博主

微信掃碼或搜索:z360901061

微信掃一掃加我為好友

QQ號聯(lián)系: 360901061

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。

【本文對您有幫助就好】

您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描上面二維碼支持博主2元、5元、10元、自定義金額等您想捐的金額吧,站長會非常 感謝您的哦!!!

發(fā)表我的評論
最新評論 總共0條評論
主站蜘蛛池模板: 亚洲综合免费视频 | 亚洲精品色综合久久 | 国产精品免费播放 | 正在播放国产乱子伦视频 | 精产国品一二二区视 | aa大片成人免费网站 | 成年女人18级毛片毛片 | 天天靠天天擦天天摸 | 欧美韩日在线 | 国产啪爱视频精品免视 | 日本一区二区在线 | 波多野结衣中文一区二区免费 | 亚洲国产欧美日韩一区二区三区 | 99国产精品九九视频免费看 | 国产精品视频专区 | 欧美十区 | 亚洲精品一区二区三区在线观看 | 在线亚洲观看 | 日日狠狠久久8888av | 日韩欧美视频在线播放 | 色噜噜狠狠狠狠色综合久一 | 91在线观 | 国产精品综合在线 | 久久精品天天中文字幕人 | 亚洲视频中文字幕在线 | 夜夜操女人| 国产一级理论免费版 | 国产精品免费一级在线观看 | 日韩精品欧美亚洲高清有无 | 99精品国产在热久久 | 伦伦影院精品一区 | 欧美高清成人 | 性视频一级 | 全免费一级毛片在线播放 | 99久久国产视频 | 亚洲一区二区精品推荐 | 天天综合久久久网 | 狠狠色噜噜狠狠狠888奇米 | 日韩国产精品视频 | 偷偷干夜夜拍 | 九九99国产精品视频 |