出处:
http://www.cnblogs.com/GavinCome/archive/2008/11/30/1307209.html来自:http://www.tracefact.net/Software-Design/MVP-Pattern-Explained.aspx
作者: 张子阳
引言
可能有的朋友已经看过我翻译的Jean-Paul Boodhoo的模型-视图-提供器 模式一文了(如果没有,建议你先看下再看这篇文章,毕竟这两篇是紧密联系的)。在那篇文章中,作者为了说明 MVP 的优点之一,易测性,引入了单元测试和NMock框架。可能有的朋友对这部分不够熟悉,也因为本人翻译水平有限,导致看后感觉不够明朗,所以我就补写了这篇文章,对作者给出的范例程序作了些许简化和整理,让我们一步步地来实现一个符合MVP模式的Web页面。
开始前的准备
在译文中,作者使用了Northwind数据库的Customer表来作为范例,这个表包含了太多的字段,而且字段类型缺乏变化,只有一个自定义的Country类型,其余均为String类型。这样容易让大家忽视掉MVP模式需要注意的一点,或者说是优势之一:视图部分,通常也就是一个Aspx页面,向用户显示的数据类型只有一种可能,就是字符串。即便你想向用户显示一个数字,比如金额,在显示之前,也会要么显式、要么隐式地转换为了字符串类型;而对象的字段类型却可能是多种多样的。所以,View的接口定义只包含String类型的Set属性,而实际将各种类型向String类型转换的工作,全部在提供器中完成。通过这样的方式,页面的CodeBehind将进一步简洁,连格式转换都移到了单独的提供器类中了。如果上面的加粗的字体你一时不能领悟也不要紧,一点点看下去你自然会明白。
本文中,我们使用一个Book类作为我们的领域对象,它将包含 字符串、日期、数字三种类型,后面我们会看到它的代码。本文的范例依然是以一个通过选择Book列表的下拉框,来显示Book的详细信息 的Web窗体页面来作说明。
现在创建一个新的空解决方案,起名为 MVP-Pattern,我们开始吧。
Model(Service)层的实现
大家可能对译文的图1和图3有点混淆,实际上图1的Service层和图3的Model层是同一个事物,它们的工作都是一样的:实际的从数据库(或者存储文件)中获取数据、填充对象,然后返回给提供器。
MVP.DTO 项目
我们先在解决方案下创建类库项目 MVP.DTO,DTO代表着Data Transfer Object(数据传输对象),这个项目和通常三层、四层构架的业务对象(Business Object)很类似,注意DTO项目实际上不应该属于Model层,它不会引用任何项目,但是因为各个层的项目都会引用它,所以我们在这里先创建它:
这个项目包含这样几个类,首先是BookDTO,它代表着我们的Book对象,它的代码如下:
publicclassBookDTO{
privateintid;// 索引
privatestringtitle;// 标题
privateDateTimepubDate;// 出版日期
privatedecimalprice;// 价格
// 构造函数 及 Get属性略...
}
接下来它还包含三个接口,这三个接口定义了传送给页面上下拉框(DropDownList)的数据,以及如何为下拉框送数据。可能正是因为它们的目的是数据传送,而不仅仅是将数据库表映射成业务对象,所以才会称之为DTO,而非Business Object吧。我们一个个来看下:
首先,我们想一想DropDownList的每个列表项ListItem需要什么数据?当然是一个Text,一个Value了,所以定义第一个接口 ILookupDTO,它代表了ListItem所需的数据,只定义了这两个属性:
publicinterfaceILookupDTO{
stringValue { get; }// 获取值
stringText { get; }// 获取文本
}
接着,给出了一个它的简单实现 SimpleLookupDTO :
publicclassSimpleLookupDTO:ILookupDTO{
privatestringvalue;
privatestringtext;
publicSimpleLookupDTO(stringvalue,stringtext) {
this.value = value;
this.text = text;
}
publicstringValue {
get {returnvalue; }
}
publicstringText {
get {returntext; }
}
}
NOTE:如果是我,我会将之命名为IListItemDTO,但是这篇文章和译文联系甚密,所以我尽量保持和译文一样的命名
接下来,我们还要要为页面上的DropDownList传送数据,所以再定义接口ILookupList:
publicinterfaceILookupList{
voidAdd(ILookupDTO dto);// 添加项目
voidClear();// 清除所有项目
ILookupDTO SelectedItem{get;}// 获得选中项目
}
在 MVP.DTO 项目中只定义了这个接口,但没有给出它的实现,因为它的实现显然和UI层很靠近,所以它的实现我们将它放到后面的 MVP.WebControls 项目(UI层)中。
最后是ILookupCollection接口及其实现。这里,我不得不批判一下这个接口的命名,它很容易让人困惑:因为List是一个集合,Collection也是一个集合,所以第一眼感觉就是ILookupCollection和 ILookupList应该是同一个事物,但是这里同时出现,让人摸不着头脑。实际上它们是完全不同的:
- ILookupList 更多的是描述了一个事物,即是页面上的DropDownList,它定义的方法也是对其本身进行操作的。
- ILookupCollection 描述的是一个行为,它仅包含一个方法,BindTo(),方法接收的参数正是ILookupList,意为将ILookupCollection的数据绑定到 ILookupList上。而ILookupCollection包含的数据,是ILookupDTO的集合(IList<ILookupDTO>,由类型外部通过构造函数传入)。
publicinterfaceILookupCollection{
voidBindTo(ILookupList list);
}
publicclassLookupCollection:ILookupCollection{
privateIList<ILookupDTO> items;
publicLookupCollection(IEnumerable<ILookupDTO> items) {
this.items =newList<ILookupDTO>(items);// 根据传递进来的items创建新的列表
}
publicintCount {
get {returnitems.Count; }// 获取项目数
}
// 将项目绑定到列表
publicvoidBindTo(ILookupList list) {
list.Clear();// 先清空列表
foreach(ILookupDTO dtoinitems) {// 遍历集合,绑定到列表中
list.Add(dto);
}
}
}
到这里 MVP.DTO 项目就结束了,我们再来看一下大家都熟悉的数据访问层,MVP.DataAccess。
MVP.DataAccess 项目
这一是和数据最接近的一层,用来获取来自数据库(或者其它存储)的数据。因为本文的目的是讲述MVP模式的构架,我们不需要把注意力集中在数据访问上,所以这一层我直接HardCode了,而非从数据库中获取。
这一层定义了一个接口 IBookMapper:
publicinterfaceIBookMapper{
IList<BookDTO> GetAllBooks();// 获取所有Book
BookDTO FindById(intbookId);// 获取某一Id的Book
}
以及一个实现了此接口的BookMapper类:
publicclassBookMapper:IBookMapper{
privatereadonlyIList<BookDTO> list;
publicBookMapper() {
list =newList<BookDTO>();
BookDTObook;
book =newBookDTO(1,"Head First Design Patterns",newDateTime(2007, 9, 12),<st1 style="PADDING-RIGHT: 0px; PADDING-LEFT: 0px; PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-TOP: 0px">67.5M</st1>);
list.Add(book);
// 略... 共添加了若干个
}
publicIList<BookDTO> GetAllBooks() {
returnnewList<BookDTO>(this.list);
}
publicBookDTO FindById(intbookId) {
foreach(BookDTO bookinlist) {
if(book.Id == bookId)
returnnewBookDTO(book.Id, book.Title, book.PubDate, book.Price);
}
returnnull;// 没有找到则返回Null
}
}
NOTE:这里有一个技巧,在GetAllBooks()和FindById()方法中,我没有直接返回list列表,或者是list中的book项目,而是对它们进行了深度复制,返回了它们的副本。这样是为了避免在类型外部通过引用类型变量访问类型内部成员。更多内容可以参考我之前写的创建常量、原子性的值类型一文(Effective C#的笔记)。
MVP.Task 项目
MVP.Task 项目是Model层的核心,之前创建的两个项目都是为这个项目进行服务的。它包含一个接口 IBookTask,这个接口定义了Task的两个主要工作:1、返回所有的Book列表(用于绑定DropDownList列表);2、根据某一个Book的Id返回该Book的详细信息。
publicinterfaceIBookTask{
ILookupCollection GetBookList();// 返回图书列表
BookDTO GetDetailsForBook(intbookId);// 返回某一图书
}
我觉得这个接口的定义是MVP模式的精华所在之一,GetDetailsForBook()方法很容易理解,我们几乎现在就可以猜到它会把工作委托给MVP.DataAccess项目的BookMapper去处理,因为BookMapper已经包含了类似的方法FindById()。关键就在于 GetBookList()方法,注意它返回的是ILookupCollection,而非一个IList<BookDTO>。这样我们在后面将介绍的提供器中,只需要在获取到的ILookupCollection上调用BindTo方法,然后传递列表对象,就可以绑定列表了,实现了Web页面和CodeBehind逻辑的分离(MVP模式的精要所在);而如果这里我们仅仅返回IList<Book>,那么绑定列表的工作势必要移交给上一层去处理。
接下来我们面临了一个问题:MVP.DataAccess 项目中的 BookMapper.GetAllBook()方法返回的是 IList<Book>,而这里需要的是一个ILookupCollection。回头看一下ILookupCollection的实现,它内部维护的是一个IList<ILookupDTO>,ILookupDTO是业务无关的,它包含了Text和Value属性用于向页面上的DropDownList的列表项提供数据。在本例中,ILookupDTO的Text应该为书名,而Value应该为书的Id。这样,我们最好能创建一个Converter类,能够进行由BookDTO到ILookupDTO,进而由IList<BookDTO> 到 IList<ILookupDTO>的转换。最后将转换好的IList<ILookupDTO>作为参数传递给ILookupCollection的构造函数,从而得到一个ILookupCollection。
注意到ILookupDTO是业务无关的,所以我们定义接口名称,为ObjectToLookupConverter,而非BookToLookupConverter。另外,以后我们可能创建其他的类型,比如Customer(客户)也能转换为LookupDTO,我们定义一个泛型接口(使得Converter类不限于BookDTO才能使用):
publicinterfaceIObjectToLookupConverter<T> {
// 将 T类型的对象obj 转换为 ILookupDTO类型
ILookupDTO ConvertFrom(T obj);
// 将 IList<T> 类型的对象列表 转换为 IList<ILookupDTO> 类型
IList<ILookupDTO> ConvertAllFrom(IList<T> obj);
}
再定义一个抽象基类实现这个接口,抽象类实现接口的ConvertAllFrom()方法,并将其中中实际的转换工作委托给 ConvertFrom() 方法:
publicabstractclassObjectToLookupConverter<T> : IObjectToLookupConverter<T> {
publicabstractILookupDTO ConvertFrom(T obj);
publicIList<ILookupDTO> ConvertAllFrom(IList<T> objList) {
List<T>list =newList<T>(objList);
returnlist.ConvertAll<ILookupDTO>(delegate(T obj) {
returnConvertFrom(obj);// 将实际的转换委托给 ConvertFrom()方法
});
}
}
最后,到了实际的将 Book 转换为 LookupDTO 的部分了,非常的简单:
publicsealedclassBookToLookupConverter: ObjectToLookupConverter<BookDTO> {
publicoverrideILookupDTO ConvertFrom(BookDTO book) {
returnnewSimpleLookupDTO(book.Id.ToString(), book.Title);
}
}
好了,有了这些准备工作,我们实现 IBookTask接口就变得轻易的多了。现在,创建MVP.Task项目的最后一个类,BookTask。注意GetBookList()方法的实现过程,和我们上面的分析一模一样:
publicclassBookTask:IBookTask{
privatereadonlyIBookMapperbookMapper;
publicBookTask()
:this(newBookMapper()) {
}
publicBookTask(IBookMapper bookMapper) {
this.bookMapper = bookMapper;
}
// 获取图书列表
publicILookupCollection GetBookList() {
IList<BookDTO> bookList = bookMapper.GetAllBooks();// 获取IList<BookDTO>
IList<ILookupDTO> list =// 转换为 IList<ILookupDTO>
newBookToLookupConverter().ConvertAllFrom(bookList);
// 构建ILookupCollection
ILookupCollectioncollection =newLookupCollection(list);
returncollection;
}
// 获取某一图书的详细信息
publicBookDTO GetDetailsForBook(intbookId) {
BookDTObook = bookMapper.FindById(bookId);
returnbook;
}
}
至此,Model层或者叫Service服务层的所有项目都已经结束了,我们接下来看MVP的V(View层)是如何构建的。
View 层的实现
Web 站点项目 和 MVP.WebControl 项目
你可能会奇怪为什么现在就讲述View层,而不是Presenter提供器层?这是因为Presenter是View 和 Model的一个协调者,从下面幅图就可以看出来。所以,我们需要先看下View层如何实现,进而才能去讨论Pesenter层。
View层包含两个项目,一个是站点项目,一个是MVP.WebControl项目,我们先看站点项目。它仅包含一个页面:Default.aspx,内容也是简单之极,我们先看页面部分的HTML代码:
<h1>MVP 模式范例</h1>
选择图书<asp:DropDownList runat="server" ID="ddlBook"></asp:DropDownList>
<br /><br />
<div style="line-height:140%;">
<strong>书名:</strong><asp:Literal ID="ltrTitle" runat="server"></asp:Literal><br />
<strong>出版日期:</strong><asp:Literal ID="ltrPubDate" runat="server"></asp:Literal><br />
<strong>价格:</strong><asp:Literal ID="ltrPrice" runat="server"></asp:Literal>
</div>
非常的简单,是吧?然后我们再看一下后置代码,通常情况下,我们会在后置代码中写DropDownList的PostBack事件,并且设置根据得到的数据填充三个Literal控件的Text属性。而在MVP模式中,这部分的工作将会交由提供器来完成,所以,我们只需要为这些控件建立Set访问器,并且将页面的引用传给提供器就可以了(如何传递页面引用给提供器后面会讨论)。我们现在在页面的后置代码中添加一组Set属性,分别去为页面的三个Literal控件赋值:
publicstringTitle {
set { ltrTitle.Text = value; }
}
publicstringPrice {
set { ltrPrice.Text = value; }
}
publicstringPubDate {
set { ltrPubDate.Text = value; }
}
通常情况下DropDownList的填充也是在后置代码中完成的,而为了能让提供器对DropDownList的数据进行填充,我们需要让这个DropDownList能够与ILookupList联系起来,并进一步通过调用来自MVP.Task中的 ILookupCollection的BindTo()方法,来对列表进行绑定。
记得到现在为止我们都没有实现 ILookupList接口,现在是时候实现它了,新建一个项目MVP.WebControl,添加对MVP.DTO的引用,然后创建ILookupList接口的实现WebLookupList。在对ILookupList接口的实现中,对DropDownList进行包装,为了更好的代码重用,我们传递DropDownList的基类ListControl,而非DropDownList本身:
publicclassWebLookupList:ILookupList{
privateListControlunderlyingList;
publicWebLookupList(ListControl underlyingList) {
this.underlyingList = underlyingList;
}
publicvoidAdd(ILookupDTO dto) {
underlyingList.Items.Add(newListItem(dto.Text, dto.Value));<br styl
分享到:
相关推荐
C# MVP模式范例解析 MVP(Model-View-Presenter)模式是一种软件设计模式,主要应用于用户界面的开发,特别是在Windows Forms或ASP.NET等环境中。这种模式将业务逻辑、数据处理和用户界面分离,提高了代码的可测试...
本案例将结合这两者,展示一个基础的MVP模式的应用。 首先,我们来了解MVP模式的基本结构。MVP分为三个主要部分: 1. **Model**:模型层,负责处理数据,与数据源交互,例如数据库或网络API。 2. **View**:视图层...
**Android MVP模式详解** 在Android应用开发中,Model-View-Presenter(MVP)模式是一种常用的架构设计模式,它有助于实现清晰的代码组织,提高代码的可测试性和可维护性。本项目“android mvp模式demo”提供了一个...
本资料"Android MVP模式实现登录操作.zip"可能包含了一个用于演示如何在Android中应用MVP模式的登录功能的实例。下面,我们将深入探讨MVP模式以及如何在实际的Android项目中实现这一模式。 1. **MVP模式的概念** ...
6. **UI绑定 - MVP模式**: Model-View-Presenter(MVP)设计模式是GWT推荐的UI组织方式。模型层负责业务逻辑,视图层负责展示,而presenter作为中间人协调两者。通过MVP,可以实现清晰的代码结构和良好的可维护性...
本篇文章将深入探讨MVP模式的核心概念,并结合Android应用实例进行解析。 **1. MVP模式的基本构成** MVP模式主要由三个组成部分组成: - **Model(模型层)**:负责处理业务逻辑和数据管理,通常与数据库、网络...
MVP模式由三个主要组件构成:Model(模型),View(视图)和Presenter(呈现器)。Model负责处理数据,View是用户界面,而Presenter作为它们之间的桥梁,处理用户交互并协调Model与View的通信。 2. 创建MVP模板的...
本Demo旨在帮助开发者理解并掌握MVP模式在实际项目中的应用。 首先,我们来详细了解一下MVP模式的三个主要组件: 1. **Model**(模型层):这一层主要负责数据的获取和处理,通常包含业务逻辑和数据访问。它可以是...
这就引出了MVP模式。 MVP(Model-View-Presenter)模式是为了改进MVC模式的不足而提出的。在MVP中,Presenter取代了Controller,它负责处理View的逻辑并与Model交互。View不再直接与Model打交道,而是通过Presenter...
《Android MVP模式实战解析》 在移动开发领域,Android应用设计模式的选择对于代码的可维护性和扩展性至关重要。其中,Model-View-Presenter(MVP)模式因其清晰的职责划分和良好的测试特性,被广泛应用于Android...
7. **安卓应用架构**:根据项目描述,可能还涉及到MVP(Model-View-Presenter)或MVVM(Model-View-ViewModel)等设计模式,以实现良好的代码组织和分离关注点。 8. **异常处理**:在处理网络请求和XML解析过程中,...
在实际案例部分,作者可能会展示如何在Android应用中有效运用这些设计模式,比如如何利用MVC、MVVM或MVP架构模式来组织代码,如何使用装饰者模式来扩展功能,或者如何利用代理模式实现动态权限控制等。这些实战经验...
通过这个简单的MVC模式小实例,我们可以学习如何将一个应用程序分解为这三个组成部分,并理解它们如何协同工作以实现高效且可维护的代码结构。在实践中,MVC模式还有许多变种和扩展,如MVVM(Model-View-ViewModel)...
MVP是一种软件设计模式,它将应用程序分为三个主要部分:Model(模型)、View(视图)和Presenter(展示者)。Model负责处理数据,View负责显示数据并捕获用户交互,而Presenter作为两者之间的桥梁,处理View与Model...
在这个特定的场景中,我们讨论的是如何使用MVP模式来实现WebView的历史记录保存与显示功能。这个功能对于一个Web应用内嵌入的Android应用尤其有用,因为它可以让用户方便地回溯之前的浏览历史。 首先,我们需要理解...
在IT行业中,MVP(Model-View-Presenter)框架是一种常用的软件设计模式,尤其是在Android应用开发中。这种模式将业务逻辑、用户界面和数据模型分离,以提高代码的可测试性和可维护性。本示例"注解MVP框架"采用了一...
而MVP模式则更适合大型项目,通过Presenter作为View和Model之间的桥梁,实现了数据和界面的解耦。 书中26个章节深度解读了23种主流设计模式,如工厂模式、单例模式、建造者模式、装饰器模式、代理模式、适配器模式...
MVP模式将应用程序分为三个部分:Model(数据模型)、View(用户界面)和Presenter(控制器)。这种方式有助于保持代码结构清晰且易于维护。 **示例代码**: ```java // Model public class LoginModel { public ...
在Android开发中,MVP(Model-View-Presenter)架构模式、Retrofit网络库和RxAndroid库是常见的组合,它们可以有效地提升应用的可维护性和性能。本篇将详细讲解这三个技术的使用方法及其结合应用。 **MVP架构模式**...
本封装是针对MVP模式进行的一次通用化处理,旨在简化开发流程,让开发者能更高效地构建Activity和Fragment。 ### MVP模式概述 1. **Model**:模型层,负责数据的获取和存储,通常与后台服务、数据库或网络接口交互...