久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

MVP 模式實(shí)例解析

 miky 2009-05-20
MVP 模式實(shí)例解析
 

2009-05-18 作者:張子陽(yáng) 來(lái)源:網(wǎng)絡(luò)

 

引言

可能有的朋友已經(jīng)看過(guò)我翻譯的Jean-Paul Boodhoo的 模型-視圖-提供器 模式 一文了(如果沒(méi)有,,建議你先看下再看這篇文章,畢竟這兩篇是緊密聯(lián)系的)。在那篇文章中,,作者為了說(shuō)明 MVP 的優(yōu)點(diǎn)之一,,易測(cè)性,引入了單元測(cè)試和NMock框架,??赡苡械呐笥褜?duì)這部分不夠熟悉,也因?yàn)楸救朔g水平有限,,導(dǎo)致看后感覺(jué)不夠明朗,,所以我就補(bǔ)寫(xiě)了這篇文章,對(duì)作者給出的范例程序作了些許簡(jiǎn)化和整理,,讓我們一步步地來(lái)實(shí)現(xiàn)一個(gè)符合MVP模式的Web頁(yè)面,。

開(kāi)始前的準(zhǔn)備

譯文中,作者使用了Northwind數(shù)據(jù)庫(kù)的Customer表來(lái)作為范例,,這個(gè)表包含了太多的字段,而且字段類(lèi)型缺乏變化,,只有一個(gè)自定義的Country類(lèi)型,,其余均為String類(lèi)型。這樣容易讓大家忽視掉MVP模式需要注意的一點(diǎn),,或者說(shuō)是優(yōu)勢(shì)之一:視圖部分,,通常也就是一個(gè)Aspx頁(yè)面,向用戶顯示的數(shù)據(jù)類(lèi)型只有一種可能,,就是字符串,。即便你想向用戶顯示一個(gè)數(shù)字,比如金額,,在顯示之前,,也會(huì)要么顯式、要么隱式地轉(zhuǎn)換為了字符串類(lèi)型,;而對(duì)象的字段類(lèi)型卻可能是多種多樣的,。所以,View的接口定義只包含String類(lèi)型的Set屬性,,而實(shí)際將各種類(lèi)型向String類(lèi)型轉(zhuǎn)換的工作,,全部在提供器中完成。通過(guò)這樣的方式,,頁(yè)面的CodeBehind將進(jìn)一步簡(jiǎn)潔,,連格式轉(zhuǎn)換都移到了單獨(dú)的提供器類(lèi)中了。如果上面的加粗的字體你一時(shí)不能領(lǐng)悟也不要緊,,一點(diǎn)點(diǎn)看下去你自然會(huì)明白,。

本文中,我們使用一個(gè)Book類(lèi)作為我們的領(lǐng)域?qū)ο螅鼘?字符串,、日期,、數(shù)字三種類(lèi)型,后面我們會(huì)看到它的代碼,。本文的范例依然是以一個(gè)通過(guò)選擇Book列表的下拉框,,來(lái)顯示Book的詳細(xì)信息 的Web窗體頁(yè)面來(lái)作說(shuō)明。

現(xiàn)在創(chuàng)建一個(gè)新的空解決方案,,起名為 MVP-Pattern,,我們開(kāi)始吧。

Model(Service)層的實(shí)現(xiàn)

大家可能對(duì)譯文的圖1和圖3有點(diǎn)混淆,,實(shí)際上圖1的Service層和圖3的Model層是同一個(gè)事物,,它們的工作都是一樣的:實(shí)際的從數(shù)據(jù)庫(kù)(或者存儲(chǔ)文件)中獲取數(shù)據(jù)、填充對(duì)象,,然后返回給提供器,。

MVP.DTO 項(xiàng)目

我們先在解決方案下創(chuàng)建類(lèi)庫(kù)項(xiàng)目 MVP.DTO,DTO代表著Data Transfer Object(數(shù)據(jù)傳輸對(duì)象),,這個(gè)項(xiàng)目和通常三層,、四層構(gòu)架的業(yè)務(wù)對(duì)象(Business Object)很類(lèi)似,注意DTO項(xiàng)目實(shí)際上不應(yīng)該屬于Model層,,它不會(huì)引用任何項(xiàng)目,,但是因?yàn)楦鱾€(gè)層的項(xiàng)目都會(huì)引用它,所以我們?cè)谶@里先創(chuàng)建它:

這個(gè)項(xiàng)目包含這樣幾個(gè)類(lèi),,首先是BookDTO,,它代表著我們的Book對(duì)象,它的代碼如下:

public class BookDTO {
    private
int id;              // 索引
    private
string title;    // 標(biāo)題
    private
DateTime pubDate; // 出版日期
    private
decimal price;       // 價(jià)格
    // 構(gòu)造函數(shù) 及 Get屬性略...
}

接下來(lái)它還包含三個(gè)接口,,這三個(gè)接口定義了 傳送 給頁(yè)面上下拉框(DropDownList)的數(shù)據(jù),,以及如何為下拉框 送數(shù)據(jù)??赡苷且?yàn)樗鼈兊哪康氖?數(shù)據(jù)傳送 ,,而不僅僅是將數(shù)據(jù)庫(kù)表映射成業(yè)務(wù)對(duì)象,所以才會(huì)稱(chēng)之為DTO,,而非Business Object吧,。我們一個(gè)個(gè)來(lái)看下:

首先,我們想一想DropDownList的每個(gè)列表項(xiàng)ListItem需要什么數(shù)據(jù),?當(dāng)然是一個(gè)Text,,一個(gè)Value了,所以定義第一個(gè)接口 ILookupDTO,,它代表了ListItem所需的數(shù)據(jù),,只定義了這兩個(gè)屬性:

public interface ILookupDTO {
    string Value { get; }    // 獲取值
    string Text { get; }     // 獲取文本
}

接著,,給出了一個(gè)它的簡(jiǎn)單實(shí)現(xiàn) SimpleLookupDTO :

public class SimpleLookupDTO : ILookupDTO {
    private
string value;
    private
string text;

    public SimpleLookupDTO(string value, string text) {
       this.value = value;
       this.text = text;
    }

    public
string Value {
       get { return value; }
    }
    public
string Text {
       get { return text; }
    }
}

NOTE:如果是我,我會(huì)將之命名為IListItemDTO,,但是這篇文章和譯文聯(lián)系甚密,,所以我盡量保持和譯文一樣的命名

接下來(lái),我們還要要為頁(yè)面上的DropDownList傳送數(shù)據(jù),,所以再定義接口ILookupList:

public interface ILookupList{
    void Add(ILookupDTO dto);       // 添加項(xiàng)目
    void Clear();                   // 清除所有項(xiàng)目
    ILookupDTO SelectedItem{get;}       // 獲得選中項(xiàng)目
}

在 MVP.DTO 項(xiàng)目中只定義了這個(gè)接口,,但沒(méi)有給出它的實(shí)現(xiàn),因?yàn)樗膶?shí)現(xiàn)顯然和UI層很靠近,,所以它的實(shí)現(xiàn)我們將它放到后面的 MVP.WebControls 項(xiàng)目(UI層)中,。

最后是ILookupCollection接口及其實(shí)現(xiàn)。這里,,我不得不批判一下這個(gè)接口的命名,,它很容易讓人困惑:因?yàn)長(zhǎng)ist是一個(gè)集合,Collection也是一個(gè)集合,,所以第一眼感覺(jué)就是ILookupCollection和 ILookupList應(yīng)該是同一個(gè)事物,,但是這里同時(shí)出現(xiàn),讓人摸不著頭腦,。實(shí)際上它們是完全不同的:

  • ILookupList 更多的是描述了一個(gè)事物,,即是頁(yè)面上的DropDownList,它定義的方法也是對(duì)其本身進(jìn)行操作的,。
  • ILookupCollection 描述的是一個(gè)行為,它僅包含一個(gè)方法,,BindTo(),,方法接收的參數(shù)正是ILookupList,意為將ILookupCollection的數(shù)據(jù)綁定到 ILookupList上,。而ILookupCollection包含的數(shù)據(jù),,是ILookupDTO的集合(IList<ILookupDTO>,由類(lèi)型外部通過(guò)構(gòu)造函數(shù)傳入),。

public interface ILookupCollection {
    void BindTo(ILookupList list);
}

public
class LookupCollection : ILookupCollection {
    private IList<ILookupDTO> items;

    public LookupCollection(IEnumerable<ILookupDTO> items) {
       this.items = new
List<ILookupDTO>(items);  // 根據(jù)傳遞進(jìn)來(lái)的items創(chuàng)建新的列表
    }

    public
int Count {
       get { return items.Count; }     // 獲取項(xiàng)目數(shù)
    }

    // 將項(xiàng)目綁定到列表
    public
void BindTo(ILookupList list) {
       list.Clear();                           // 先清空列表
       foreach (ILookupDTO dto in items) {     // 遍歷集合,,綁定到列表中
           list.Add(dto);
       }
    }
}

到這里 MVP.DTO 項(xiàng)目就結(jié)束了,我們?cè)賮?lái)看一下大家都熟悉的數(shù)據(jù)訪問(wèn)層,,MVP.DataAccess,。

MVP.DataAccess 項(xiàng)目

這一是和數(shù)據(jù)最接近的一層,用來(lái)獲取來(lái)自數(shù)據(jù)庫(kù)(或者其它存儲(chǔ))的數(shù)據(jù),。因?yàn)楸疚牡哪康氖侵v述MVP模式的構(gòu)架,,我們不需要把注意力集中在數(shù)據(jù)訪問(wèn)上,所以這一層我直接HardCode了,,而非從數(shù)據(jù)庫(kù)中獲取,。

這一層定義了一個(gè)接口 IBookMapper:

public interface IBookMapper {
    IList<BookDTO> GetAllBooks();           // 獲取所有Book
    BookDTO FindById(int bookId);           // 獲取某一Id的Book
}

以及一個(gè)實(shí)現(xiàn)了此接口的BookMapper類(lèi):

public class BookMapper :IBookMapper {
    private
readonly IList<BookDTO> list;

    public BookMapper() {
       list = new
List<BookDTO>();

       BookDTO book;
       book = new
BookDTO(1, "Head First Design Patterns", new DateTime(2007, 9, 12), 67.5M);
       list.Add(book);
       // 略... 共添加了若干個(gè)
    }

    public IList<BookDTO> GetAllBooks() {
       return
new List<BookDTO>(this.list);
    }

    public BookDTO FindById(int bookId) {
       foreach (BookDTO book in list) {
           if (book.Id == bookId)
              return
new BookDTO(book.Id, book.Title, book.PubDate, book.Price);
       }

       return
null;      // 沒(méi)有找到則返回Null
    }
}

NOTE:這里有一個(gè)技巧,在GetAllBooks()和FindById()方法中,我沒(méi)有直接返回list列表,,或者是list中的book項(xiàng)目,,而是對(duì)它們進(jìn)行了深度復(fù)制,返回了它們的副本,。這樣是為了避免在類(lèi)型外部通過(guò)引用類(lèi)型變量訪問(wèn)類(lèi)型內(nèi)部成員,。更多內(nèi)容可以參考我之前寫(xiě)的 創(chuàng)建常量、原子性的值類(lèi)型 一文(Effective C#的筆記),。

MVP.Task 項(xiàng)目

MVP.Task 項(xiàng)目是Model層的核心,,之前創(chuàng)建的兩個(gè)項(xiàng)目都是為這個(gè)項(xiàng)目進(jìn)行服務(wù)的。它包含一個(gè)接口 IBookTask,,這個(gè)接口定義了Task的兩個(gè)主要工作:1,、返回所有的Book列表(用于綁定DropDownList列表);2,、根據(jù)某一個(gè)Book的Id返回該Book的詳細(xì)信息,。

public interface IBookTask {
    ILookupCollection GetBookList();        // 返回圖書(shū)列表
    BookDTO GetDetailsForBook(int bookId);     // 返回某一圖書(shū)
}

我覺(jué)得這個(gè)接口的定義是MVP模式的精華所在之一,GetDetailsForBook()方法很容易理解,,我們幾乎現(xiàn)在就可以猜到它會(huì)把工作委托給MVP.DataAccess項(xiàng)目的BookMapper去處理,,因?yàn)锽ookMapper已經(jīng)包含了類(lèi)似的方法FindById()。關(guān)鍵就在于 GetBookList()方法,,注意它返回的是ILookupCollection,,而非一個(gè)IList<BookDTO>。這樣我們?cè)诤竺鎸⒔榻B的提供器中,,只需要在獲取到的ILookupCollection上調(diào)用BindTo方法,,然后傳遞列表對(duì)象,就可以綁定列表了,,實(shí)現(xiàn)了Web頁(yè)面和CodeBehind邏輯的分離(MVP模式的精要所在),;而如果這里我們僅僅返回IList<Book>,那么綁定列表的工作勢(shì)必要移交給上一層去處理,。

接下來(lái)我們面臨了一個(gè)問(wèn)題:MVP.DataAccess 項(xiàng)目中的 BookMapper.GetAllBook()方法返回的是 IList<Book>,,而這里需要的是一個(gè)ILookupCollection?;仡^看一下ILookupCollection的實(shí)現(xiàn),,它內(nèi)部維護(hù)的是一個(gè)IList<ILookupDTO>,ILookupDTO是業(yè)務(wù)無(wú)關(guān)的,,它包含了Text和Value屬性用于向頁(yè)面上的DropDownList的列表項(xiàng)提供數(shù)據(jù),。在本例中,ILookupDTO的Text應(yīng)該為書(shū)名,,而Value應(yīng)該為書(shū)的Id,。這樣,,我們最好能創(chuàng)建一個(gè)Converter類(lèi),能夠進(jìn)行由BookDTO到ILookupDTO,,進(jìn)而由IList<BookDTO> 到 IList<ILookupDTO>的轉(zhuǎn)換,。最后將轉(zhuǎn)換好的IList<ILookupDTO>作為參數(shù)傳遞給ILookupCollection的構(gòu)造函數(shù),從而得到一個(gè)ILookupCollection,。

注意到ILookupDTO是業(yè)務(wù)無(wú)關(guān)的,,所以我們定義接口名稱(chēng),為ObjectToLookupConverter,,而非BookToLookupConverter,。另外,以后我們可能創(chuàng)建其他的類(lèi)型,,比如Customer(客戶)也能轉(zhuǎn)換為L(zhǎng)ookupDTO,,我們定義一個(gè)泛型接口(使得Converter類(lèi)不限于BookDTO才能使用):

public interface IObjectToLookupConverter<T> {
    // 將 T類(lèi)型的對(duì)象obj 轉(zhuǎn)換為 ILookupDTO類(lèi)型
    ILookupDTO ConvertFrom(T obj);

    // 將 IList<T> 類(lèi)型的對(duì)象列表 轉(zhuǎn)換為 IList<ILookupDTO> 類(lèi)型
    IList<ILookupDTO> ConvertAllFrom(IList<T> obj);
}

再定義一個(gè)抽象基類(lèi)實(shí)現(xiàn)這個(gè)接口,抽象類(lèi)實(shí)現(xiàn)接口的ConvertAllFrom()方法,,并將其中中實(shí)際的轉(zhuǎn)換工作委托給 ConvertFrom() 方法:

public abstract class ObjectToLookupConverter<T> : IObjectToLookupConverter<T> {
    public
abstract ILookupDTO ConvertFrom(T obj);

    public IList<ILookupDTO> ConvertAllFrom(IList<T> objList) {
       List<T> list = new
List<T>(objList);
      
       return list.ConvertAll<ILookupDTO>(delegate(T obj) {
           return ConvertFrom(obj);     // 將實(shí)際的轉(zhuǎn)換委托給 ConvertFrom()方法
       });
    }
}

最后,,到了實(shí)際的將 Book 轉(zhuǎn)換為 LookupDTO 的部分了,非常的簡(jiǎn)單:

public sealed class BookToLookupConverter : ObjectToLookupConverter<BookDTO> {
    public
override ILookupDTO ConvertFrom(BookDTO book) {
       return
new SimpleLookupDTO(book.Id.ToString(), book.Title);
    }
}

好了,,有了這些準(zhǔn)備工作,,我們實(shí)現(xiàn) IBookTask接口就變得輕易的多了。現(xiàn)在,,創(chuàng)建MVP.Task項(xiàng)目的最后一個(gè)類(lèi),,BookTask。注意GetBookList()方法的實(shí)現(xiàn)過(guò)程,,和我們上面的分析一模一樣:

public class BookTask : IBookTask {
    private
readonly IBookMapper bookMapper;

    public BookTask()
       : this(new BookMapper()) {
    }

    public BookTask(IBookMapper bookMapper) {
       this.bookMapper = bookMapper;
    }

    // 獲取圖書(shū)列表
    public ILookupCollection GetBookList() {

       IList<BookDTO> bookList = bookMapper.GetAllBooks();// 獲取IList<BookDTO>
       IList<ILookupDTO> list = // 轉(zhuǎn)換為 IList<ILookupDTO>
           new BookToLookupConverter().ConvertAllFrom(bookList);

       // 構(gòu)建ILookupCollection
       ILookupCollection collection = new
LookupCollection(list);

       return collection;
    }

    // 獲取某一圖書(shū)的詳細(xì)信息
    public BookDTO GetDetailsForBook(int bookId) {
       BookDTO book = bookMapper.FindById(bookId);
       return book;
    }
}

至此,,Model層或者叫Service服務(wù)層的所有項(xiàng)目都已經(jīng)結(jié)束了,我們接下來(lái)看MVP的V(View層)是如何構(gòu)建的,。

View 層的實(shí)現(xiàn)

Web 站點(diǎn)項(xiàng)目 和 MVP.WebControl 項(xiàng)目

你可能會(huì)奇怪為什么現(xiàn)在就講述View層,而不是Presenter提供器層,?這是因?yàn)镻resenter是View 和 Model的一個(gè)協(xié)調(diào)者,,從下面幅圖就可以看出來(lái)。所以,,我們需要先看下View層如何實(shí)現(xiàn),,進(jìn)而才能去討論P(yáng)esenter層。

View層包含兩個(gè)項(xiàng)目,,一個(gè)是站點(diǎn)項(xiàng)目,,一個(gè)是MVP.WebControl項(xiàng)目,我們先看站點(diǎn)項(xiàng)目,。它僅包含一個(gè)頁(yè)面:Default.aspx,,內(nèi)容也是簡(jiǎn)單之極,,我們先看頁(yè)面部分的HTML代碼:

<h1>MVP 模式范例</h1>
選擇圖書(shū)<asp:DropDownList runat="server" ID="ddlBook"></asp:DropDownList>
<br /><br />
<div style="line-height:140%;">
    <strong>書(shū)名:</strong><asp:Literal ID="ltrTitle" runat="server"></asp:Literal><br />
    <strong>出版日期:</strong><asp:Literal ID="ltrPubDate" runat="server"></asp:Literal><br />
    <strong>價(jià)格:</strong><asp:Literal ID="ltrPrice" runat="server"></asp:Literal>
</div>

非常的簡(jiǎn)單,是吧,?然后我們?cè)倏匆幌潞笾么a,,通常情況下,我們會(huì)在后置代碼中寫(xiě)DropDownList的PostBack事件,,并且設(shè)置根據(jù)得到的數(shù)據(jù)填充三個(gè)Literal控件的Text屬性,。而在MVP模式中,這部分的工作將會(huì)交由提供器來(lái)完成,,所以,,我們只需要為這些控件建立Set訪問(wèn)器,并且將頁(yè)面的引用傳給提供器就可以了(如何傳遞頁(yè)面引用給提供器后面會(huì)討論),。我們現(xiàn)在在頁(yè)面的后置代碼中添加一組Set屬性,,分別去為頁(yè)面的三個(gè)Literal控件賦值:

public string Title {
    set { ltrTitle.Text = value; }
}

public
string Price {
    set { ltrPrice.Text = value; }
}

public
string PubDate {
    set { ltrPubDate.Text = value; }
}

通常情況下DropDownList的填充也是在后置代碼中完成的,而為了能讓提供器對(duì)DropDownList的數(shù)據(jù)進(jìn)行填充,,我們需要讓這個(gè)DropDownList能夠與ILookupList聯(lián)系起來(lái),,并進(jìn)一步通過(guò)調(diào)用來(lái)自MVP.Task中的 ILookupCollection的BindTo()方法,來(lái)對(duì)列表進(jìn)行綁定,。

記得到現(xiàn)在為止我們都沒(méi)有實(shí)現(xiàn) ILookupList接口,,現(xiàn)在是時(shí)候?qū)崿F(xiàn)它了,新建一個(gè)項(xiàng)目MVP.WebControl,,添加對(duì)MVP.DTO的引用,,然后創(chuàng)建ILookupList接口的實(shí)現(xiàn)WebLookupList。在對(duì)ILookupList接口的實(shí)現(xiàn)中,,對(duì)DropDownList進(jìn)行包裝,,為了更好的代碼重用,我們傳遞DropDownList的基類(lèi)ListControl,,而非DropDownList本身:

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

    public
void Clear() {
       underlyingList.Items.Clear();
    }

    public ILookupDTO SelectedItem {
       get {
           ListItem item = underlyingList.SelectedItem;
           return
new SimpleLookupDTO(item.Value, item.Text);
       }
    }
}

可以看到我們實(shí)際上將對(duì)這個(gè)接口實(shí)現(xiàn)的具體工作都委托給了 ListControl,,這樣,當(dāng)我們?cè)贗LookupList上調(diào)用Add()方法添加列表項(xiàng)時(shí),,便會(huì)添加到頁(yè)面的DropDownList上,。

記住:我們期望能讓提供器送數(shù)據(jù)的所有Web頁(yè)面上的控件,,都應(yīng)該為提供器提供一個(gè)入口,。在前面,我們?yōu)槿齻€(gè)Literal空間提供的入口是Set屬性,。這里我們一樣需要提供一個(gè)Get屬性,,來(lái)讓提供器能夠獲得一個(gè)ILookupList。在Default頁(yè)面的后置代碼中添加下面代碼:

public ILookupList BookList {
    get { return
new WebLookupList(ddlBook); }
}

Presenter 層的實(shí)現(xiàn)

實(shí)現(xiàn)Presenter(提供器)之前我們先考慮它的作用是什么:從Task中獲取數(shù)據(jù),,然后送到View層(Aspx頁(yè)面)中,。這就暗示 提供器必須包含 Task和 View層的引用,。但是如果我們是無(wú)法讓提供器引用站點(diǎn)項(xiàng)目的,因?yàn)檎军c(diǎn)項(xiàng)目不會(huì)生成單獨(dú)的dll文件(基于每個(gè)頁(yè)面生成dll),。但是站點(diǎn)卻可以引用提供器,,所以我們只要在提供器項(xiàng)目中定義一個(gè)接口,然后讓頁(yè)面去實(shí)現(xiàn)這個(gè)接口,,我們通過(guò)這個(gè)接口去為頁(yè)面送數(shù)據(jù)(調(diào)用接口的Set訪問(wèn)器),。

MVP.Presentation 項(xiàng)目

現(xiàn)在你可以將頁(yè)面上的三個(gè)Literal和一個(gè)DropDownList與這個(gè)View接口聯(lián)系起來(lái)了。創(chuàng)建MVP.Presentation項(xiàng)目,,然后我們定義Default頁(yè)面需要實(shí)現(xiàn)的IViewBookView接口:

public interface IViewBookView {
    ILookupList BookList { get; }
    string Title { set; }
    string PubDate { set; }
    string Price { set; }
}

這個(gè)接口的定義完全是基于Web頁(yè)面的,,你需要為頁(yè)面提供哪些數(shù)據(jù),或者為哪個(gè)控件送數(shù)據(jù),,那么就定義哪些屬性,。然后我們讓W(xué)eb項(xiàng)目引用MVP.Presentation項(xiàng)目,在修改頁(yè)面的后置代碼文件Default.aspx.cs,,讓它去實(shí)現(xiàn)這個(gè)接口(因?yàn)轫?yè)面已經(jīng)包含了這個(gè)接口的所有定義,,所以這里只是起到一個(gè)向提供器傳遞窗體的作用)。

public partial class _Default : System.Web.UI.Page, IViewBookView

下一步,,我們要實(shí)現(xiàn)提供器,,我們?cè)陧?xiàng)目中再添加一個(gè)文件 ViewBookPresenter.cs,添加下面代碼:

public class ViewBookPresenter {
    private
readonly IViewBookView view;
    private
readonly IBookTask task;

    public ViewBookPresenter(IViewBookView view) : this(view, new BookTask()) { }

    public ViewBookPresenter(IViewBookView view, IBookTask task) {
       this.view = view;
       this.task = task;
    }

    // 初始化方法,,綁定列表
    public
void Initialize() {
       ILookupCollection collection = task.GetBookList(); // 獲取圖書(shū)列表
       collection.BindTo(view.BookList);   // 綁定到列表
       DisplayBookDetails();               // 顯示圖書(shū)信息
    }

    // 獲取選中的圖書(shū)的Id
    private
int? SelectedBookId {
       get {
           string selectedId = view.BookList.SelectedItem.Value;

           if (String.IsNullOrEmpty(selectedId)) return
null;

           int? id = null;

           try {
              id = int.Parse(selectedId.Trim());
           } catch (FormatException) { }

           return id;
       }
    }

    // 顯示特定圖書(shū)的詳細(xì)信息
    public
void DisplayBookDetails() {
       int? bookId = SelectedBookId;

       if (bookId.HasValue) {
           BookDTO book = task.GetDetailsForBook(bookId.Value);
           UpdateViewFrom(book);
       }
    }

    // 更新頁(yè)面的信息,,在這里進(jìn)行格式化
    private
void UpdateViewFrom(BookDTO book) {
       view.Price = book.Price.ToString("c");        
       view.PubDate = String.Format(new DateFomatter(), "{0}", book.PubDate);
       view.Title = book.Title;
    }

    // 格式日期,作為示范,,所有格式化工作都放到 Presenter中
    private
class DateFomatter : ICustomFormatter, IFormatProvider {

       public
string Format(string format, object arg, IFormatProvider formatProvider) {
           DateTime date = (DateTime)arg;
           return
string.Format("{0}年{1}月{2}日", date.Year, date.Month, date.Day);       
       }
      
       public
object GetFormat(Type formatType) {
           return
this;
       }
    }
}

上面的代碼是很直白的,,只有一個(gè)主題思想:從task中獲取數(shù)據(jù),然后調(diào)用view接口的屬性,,或者從view接口獲得DropDownList的引用(通過(guò)ILookupList),,然后通過(guò) BindTo()方法為列表填充數(shù)據(jù)。注意到Initialize()方法,,它為列表填充數(shù)據(jù),,這個(gè)應(yīng)該在頁(yè)面加載之前就被調(diào)用;還有DisplayBookDetails()方法,,它應(yīng)該在列表的SelectedIndexChanged事件被觸發(fā)時(shí)調(diào)用,,所以我們還有最后一部沒(méi)有做,,再次修改Default.aspx.cs文件,,設(shè)置這些方法的觸發(fā)時(shí)機(jī)。

最后一步,,再次修改Default.aspx.cs文件

在后置代碼類(lèi)中添加如下代碼,,完成上一小節(jié)說(shuō)明的所有內(nèi)容:

private ViewBookPresenter presenter;

protected
override void OnInit(EventArgs e) {
    base.OnInit(e);
    presenter = new
ViewBookPresenter(this);       // 創(chuàng)建Presenter的實(shí)例

    // 為DropDownList綁定事件處理方法
    ddlBook.SelectedIndexChanged += delegate {
       presenter.DisplayBookDetails();
    };
}

protected
void Page_Load(object sender, EventArgs e) {
    if (!IsPostBack) {
       presenter.Initialize();      // 綁定列表
    }
}

這里值得注意的是 ViewBookPresenter 對(duì)象的創(chuàng)建,,它通過(guò)this關(guān)鍵字,將頁(yè)面本身傳遞了進(jìn)去,,而頁(yè)面本身實(shí)現(xiàn)了IViewBookView接口,,滿足構(gòu)造函數(shù)的簽名,這樣提供器通過(guò)IViewBookView便可以訪問(wèn)頁(yè)面上的屬性和列表,,并為之提供數(shù)據(jù),。

總結(jié)

這篇文章是對(duì) 模型-視圖-提供器 模式 一文范例程序的一個(gè)刨析和說(shuō)明。在本文中,,我們創(chuàng)建了一個(gè)包含多個(gè)項(xiàng)目的完整的符合MVP模式的Web頁(yè)面,。我們先創(chuàng)建了基礎(chǔ)項(xiàng)目 MVP.DTO,用于傳送數(shù)據(jù),、MVP.DataAccess,,用于數(shù)據(jù)訪問(wèn);接著分別創(chuàng)建了 Model層,、View層,、Presenter層,并講述了它們之間的調(diào)用關(guān)系,,以及使用的要點(diǎn),。通過(guò)這則范例,希望大家能對(duì)MVP模式有了一定的認(rèn)識(shí)和了解,。

不一定項(xiàng)目的每個(gè)頁(yè)面,,都去采用MVP模式來(lái)構(gòu)建。但如果運(yùn)用的好的話,,可以將多個(gè)頁(yè)面共同的的某一部分(或者叫功能)抽象出來(lái),,使用同一個(gè)提供器,可以很大程度上實(shí)現(xiàn)代碼重用,。另外也可以一個(gè)Page實(shí)現(xiàn)多個(gè)IView,,將頁(yè)面功能分離成多個(gè)部分,需要使用哪個(gè)功能,,就實(shí)現(xiàn)哪個(gè)IView,,并使用相應(yīng)的IViewPresenter進(jìn)行初始化。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,,所有內(nèi)容均由用戶發(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)遵守用戶 評(píng)論公約

    類(lèi)似文章 更多