版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
http://xiekeli.blogbus.com/logs/5739777.html
最近一直在看关于企业应用架构方面的资料,不久前在网上看到一篇关于MVP模式, 决定好好翻译一下,一来加深理解,二来可以让大家对我的翻译内容指点迷经,提高一下可怜的E文水平。
By Billy McCafferty.
MVP模式使用了1年多了,对于使用MVP模式,我觉得很愉快,所以决定在ASP.NET projects上极力推荐一下。我发现用MVP模式构建的系统比我以前用过的其他方法写的,Bug更少,更稳定,而且更容易维护。我坚定的相信MVP模 式是构建(a homegrown )ASP.NET应用程序的最好方法。作为替代品,也可以考虑Castle Project's MonoRail MVC framework, 因为MonoRail 比MVP模式更简单更干净,但是他完全摈弃了ASP.NET原有的页面生命周期。此外,MonoRail 使得使用第三方的ASP.NET的用户控件变得更难。不过当你试用了以后,也许会觉得这两个所谓的缺点并没有想象的那么坏。
假设你为你的ASP.NET应用程序选择了MVP模式,必须注意有两点和本文的内容不同:(当你读完本文余下部分内容,这两点注意项会显得更有意义)第 一.我发现把一个领域对象的ID暴露为共有公有的,并不是一个好的习惯。至于其缺点我在我的另一篇文章NHibernate Best Practices.中已经说得很详细了。对于ID最多只能提供一个protected 的setter方法,并且只能被数据访问层(data-access layer)调用。但是,在单元测试中需要设置ID,NHibernate Best Practices中讨论了怎么样用反射来完成。Another lesson learned over the past year is that it's not a good practice to add both a getter and a setter for, firstly, initializing the view with a domain object and then, secondly, retrieving the updated domain object for persistence on add/edit pages.(这句话的意思我不是很确定,我的翻译:过去几年中得到的另一个教训是为:为领域对象提供getter和setter方法,首先在初始化领域 对象的显示,然后在一个添加/修改页面中获得需要更新的领域对象,这并不是一个好的习惯)虽然Setter方法是必须的,但getter方法往往会带来很 多问题。关于这个问题的缺点和对应的解决方法已经在这篇文章中进行了讨论:http://devlicio.us/blogs/billy_mccafferty/archive/2007/03/19/considering-a-view-s-get-set-roles-with-mvp.aspx
带着上面两个问题,看接下来的文章,将成为弄清在ASP.NET中MVP模式应用的杠杆,如果需要一个更加广泛的MVP模式应用实例,并且使用过NHibernate 1.2,那么一定要看一下NHibernate Best Practices 这篇文章中的企业级的代码。
介绍
经历了几年的上千行的意大利面条式的ASP代码,微软终于为我们提供了一个web开发平台---ASP.NET, ASP.NET通过code-behind 技术立刻带来了表现层和业务逻辑的分离。虽然微软的目的是好的,而且对一般的应用程序也有较好的表现,但是在开发企业级WEB应用程序的时候,code- behind 在很多方面都还比较欠缺:
1.code-behind 带来了表现层、业务逻辑、数据库访问层代码的混合。这是因为code-behind常常扮演事件处理器(event handler)、工作流控制器(a workflow controller)、表现层和业务逻辑层的中介者(mediator )、表现层和数据访问层的中介者(mediator ).赋予code-behind 这么多职责往往会带来难以管理的代码。在企业级应用的开发中,好的设计必须遵循一个原则:在各层之间保持适当的分离,尽可能的保持code-behind 的职责单一(keep the code-behind as clean as possible)。用Model-View-Presenter模式,我们将看到code-behind职责非常的单一化,并且对表现层的细节严格管理(kept strictly to managing presentation details.)
2.code-behind 模式的另一个缺点是,如果不利用helper/utility 之类的类重复的代码抽离出来,在code-behind页间将很难对表现层逻辑进行重用。显然,这也是一个妥善的解决方案。但是这又常常导致低内聚的类, 就象ASP中包含很多其他的对象一样。正确的设计,每个类都应该是高内聚、有单一的职责---如果把一个类命名为 ContainsDuplicatePresentationCodeBetweenThisAndThat.cs ,就不合格了。
3.由于code-behind页是和表现层页(aspx)紧密绑定的,所以几乎就很难进行单元测试。虽然也可以选择如:NUnitAsp 之类的产品进行测试。但是相当的耗时,影响单元测试的性能---单元测试应该是很简单快速的。
我们可以使用很多种技术来实现一个很好的分离。比如:Castle MonoRail项目,旨在模仿象Ruby-On-Rails这样的框架所能带来的好处,不过它摈弃了事件模型。 Maverick.NET虽然可以轻易的实现事件模型,但他把code-behind作为控制器来使用。理想的解决方案应该是应该是继续支持ASP.NET的事件模型,并尽可能的简单。Model-View-Presenter模式正是在没有依靠任何第三方框架的情况下实现了这个目的。
Model-View-Presenter
Model-View-Presenter (MVP)是Model-View-Controller (MVC) 模式的一种变种。是在MVC模式的基础上针对如ASP.NET这样的支持事件模式的页面作了调整。MVP源于 Dolphin Smalltalk,MVP 最大的区别终于MVP实现了一种MVC的观察者设计,但他们的基本思想还是一样的,模型(Model)存储数据,表现层显示模型,而传递者 (presenter )负责各层的通信。MVP实现了一种观察者的方法,即:传递者解析事件,并执行所必须的逻辑将事件映射到对应的命令(commands),操作对应 Model。如果向知道更多关于MVC vs. MVP之间的关系,可以看一下Darron Schall's 的文章:http://www.darronschall.com/weblog/archives/000113.cfm,接下来是一个关于MVP的详细的例子。
作者提示:Martin Fowler 已经建议将MVP模式分为两个模式:Supervising Controller 和 Passive View。关于这一分离的简短大纲可以看:http://geekswithblogs.net/billy/archive/2006/07/20/85815.aspx
The content described herein is more consistent with Supervising Controller as the View is aware of the Model。
一个价值不高的例子
Download trivial example of MVP
在这个例程中,客户端想在页面上显示当前的时间。ASPX 页面因为要显示时间,所以是表现层(View)。传递者负责决定当前时间(Model),并把当前时间交给View。我们从一个单元测试开始:
上面的单元测试和图说明了MVP中各个元素间的关系。第一行代码创建了一个MockCurrentTimeView实例。从这个单元测试可以看到,所有的传递者逻辑(Presenter logic )可以在没有ASPX页的情况下被测试。所需要的是实现一个View接口的对象,因此,一个mock view代替了实际View的位置。
下一行创建了一个传递者对象实例,在其构造函数中传递了一个实现了ICurrentTimeView接口的对象。这样传递者就可以操作View了。从图上 可以看到,传递者只是和View接口打交道,而并没有直接面对具体的实现者,这样就可以实现View的多态,通过同一个传递者操作多个View。
最后调用了传递者的InitView()方法,这个方法取得了当前的时间,并通过在ICurrentTimeView定义的Public属性将当前时间传 给View。然后在单元测试中断言View中的CurrentTime应该比他的原始值大。现在所要做的是编译这个单元测试,并让其通过。
ICurrentTimeView.cs - The View Interface
接上面第一步编译,然后需要创建ICurrentTimeView.cs ,这个View接口提供了传递者和View之间通信的管道。这个View接口需要暴露一个Public属性,以便传递者可以将当前时间(即:Model)传递给View。
View对于当前时间只需要一个setter方法,因为View只需要显示Model。但提供一个getter方法,可以在单元测试中对CurrentTime 经行检查。这个getter方法可以加在MockCurrentTimeView中而不是ICurrentTimeView中。这样既可以在单元测试用检查CurrentTime,而又不用强行在ICurrentTimeView中添加getter了。
CurrentTimePresenter.cs - 传递者
传递者将处理与Model通信的逻辑,并把Model的值传递给View。传递者需要使单元测试通过,就象下面:
当上面这些都已经OK了,单元测试就可以顺利通过了!下一步就是创建ASPX 页面,实现真正的View。
注意:需要ArgumentNullException检查,这就是所谓的面向契约的设计。在你整个代码中加上这样的检查将大大降低Bug的捕捉。关于更多的面向契约的设计可以看:http://archive.eiffel.com/doc/manuals/technology/contract和http://www.codeproject.com/csharp/designbycontract.asp
ShowMeTheTime.aspx - The View
实际的View需要做以下几项:
1.ASPX页面需要一个显示当前时间的途径。如下所示,一个简单的Lable用来显示当前时间。
2.code-behind必须实现ICurrentTimeView接口。
3.code-behind必须创建传递者(Presenter),并将自己(View)传递到传递者的构造函数中。
4.创建完传递者后,需要调用InitView() 来完成MVP的周期。
The ASPX Page:
The ASPX Code-Behind Page:
Is that it?
上 面例子似乎有点缺限:为不起眼的好处平添了这么多的工作。我们从原来的只有一个ASPX页到现在的一个传递者类、一个View 接口、一个单元测试类。收获就是对传递者类可以进行单元测试,即可以很便利的对code-behind 进行单元测试。因为这是一个价值不高的例子,MVP 的闪亮点需要在企业级应用中才能展现出来,而不是"hello world"之类的示例程序中。接下来的主体将详细描述MVP在企业级应用的作用。
MVP Within Enterprise ASP.NET Applications
Encapsulating Views with User Controls:这个主题将讨论用用户控件作为View的MVP实现方法
Event Handling with MVP: 这个主题将讨论将事件传送给传递者以及关于页面的验证,判断是否IsPostBack ,将消息传回View。
Page Redirects with MVP & PageMethods:这个主题包括怎样用PageMethods ,以用户控件作为View的方式,实现页面的重定向。
Presentation Security with MVP: 这个主题讨论一个View的局部部分隐藏/显示的基本安全约束。
Application Architecture with MVP (Advanced): 这个主题将推荐一种MVP的实现方法,即完全基于NHibernate的数据访问层。
I.用用户控件封装View
在上面的例子中,ASPX页面扮演了View的角色。在这里用ASPX已经足够了,因为这里的目的很简单--显示当前时间。然而在更多有代表性的项目中, 一个页面由很多实现不同功能的区块组成,无论是WebParts、user controls等等...在这些典型企业应用中,很重要的一点是把功能模块分离,以达到复用。在MVP模式中,用user controls封装View,而ASPX 页则用于View的初始化和页面的重定向。接着上面的例子,我们只需要对ASPX进行修改,以实现这种变化。这也是MVP的另一个好处,很多的变化只需要 在View层进行修改,而不用Presenter 层和Model层进行修改。
ShowMeTheTime.aspx Redux - The View Initializer
通过这种方法,把user controls作为View,ShowMeTheTime.aspx的职责有以下几点:
1.ASPX页面需要声明实现了ICurrentTimeView接口的用户控件。
2.在ASPX code-behind 代码中创建传递者对象(Presenter),并把用户控件传递到传递者的构造函数中。
3.把View传递给传递者后,ASPX需要调用InitView() 方法,以完成MVP的整个周期。
The ASPX Page:
The ASPX Code-Behind Page:
CurrentTimeView.ascx - The User Control-as-View
现在user control 就是真正的View了, 尽可能的dumb(这里是“dump”不知道实际表示什么意思)- 这也正是我们想要的View。
The ASCX Page:
The ASCX Code-Behind Page:
User Control-as-View Approach的正反面
显 然,User Control-as-View approach 的主要缺点是:在整个系统中又增加了一块,现在正MVP的关系由以下几部分组成:单元测试、传递者、view 接口、view 的实现(the user control)和view 的初始化器(the ASPX page)。增加了这个附加层间接的增加了设计的复杂度。而user control-as-View 的好处有以下几点:
1.使得View在不同页面间转移变得更简单,而这在大型的web应用中是很常见的。
2.使得在不同的ASPX页间复用变得更简单,而且不用复制大量的代码。
3.View 可以在不同的ASPX 页中实现不同的初始化,例如:一个 user control可以用来显示一个项目列表,在站点的报表部分,用户可以浏览和筛选所有有用的项目,而在站点的其他地方,用户只能浏览子项目,而不能进行筛 选。在实现的时候,把同样的View传递给同样的presenter,但每一个ASPX 页,在其各自的站点区域中,以不同的方法调用不同的initialize。
4.不用增加太多额外的代码,就可以在ASPX中添加其他的 Views,在ASPX中简单的包含后,在code-behind代码中,将这些 user control链接到Presenter 中,在一个ASPX 页中添加更多的功能模块,如果不使用user control,是件令人头痛的事。
这几天,忙于测试自己写的程序,结果把这篇翻译给落下了,真是惭愧,一篇文章翻了那么久。今天终于把程序中的一条大bug给挑出来了,虽然有点累了,不过还是把这篇E文给翻译完吧。
MVP的事件处理(II. Event Handling with MVP)
Download simple Event-Handling MVP project
前 一个例子本质上描述了Presenter 和View之间的一种单向通信。Presenter与View之间通信,并将Model 传递给View,在多数情况下,触发的事件需要交由Presenter进行处理,而且一些事件的处理取决于窗体的合法性以及是否处于IsPostBack 状态,例如:象数据绑定(data-binding),在IsPostBack状态下是不需要做的。
声 明:Page.IsPostBack 和Page.IsValid是Web应用程序中特有的关键字,因此,下面所描述的presenter 层在非Web项目中可能是非法的。不过只要作细微的改动,就可在WebForms、WinForms 或mobile 项目中顺利运行。无论在何种项目中,其中的思想是相同的,所以我还是建议使Presenter在.net环境中可移植。
一个简单的序列图:
继 续先前的例子,假设现在需要使用输入一个天数,并将天数加到当前时间上,在View上显示的时间为加上用户输入天数的时间,假设用户输入的是有效的数字。 在非IsPostBack状态下,View应该显示当前时间,当处于IsPostBack状态时,Presenter应该响应相应的事件。下面的序列图显 示用户初始请求时发生了什么以及用户按下“Add Days”按钮时发生了什么。后面是对这一序列图的详细说明:
A) User Control-as-View Created
这一步只是表示在ASPX页面中声明的用户控件。在页面初始过程中,用户控件得到了创建。在图中强调了用户控件实现了ICurrentTimeView接口这一事实。在Page_Load方法中,在code-behind 代码中,创建了一个Presenter的实例,将用户控件作为参数传递到其构造函数中,到目前为止,每件事和在“Encapsulating Views with User Controls”一节中所描述的是一样的。
B) Presenter Attached to View
为了将一个事件从用户控件(View)传递到Presenter中,View必须包含一个CurrentTimePresenter对象的引用。为此,ShowMeTheTime.aspx的初始化中,将Presenter 传递给View,已备用。这并不会造成Presenter与View的双向(循环)依赖。因为,Presenter 依赖于View 的接口,而View的实现为了传递事件而依赖于Presenter。让我们在代码中看他们是如何工作的:
ICurrentTimeView.cs - The View Interface
public interface ICurrentTimeView {
DateTime CurrentTime { set; }
string Message { set; }
void AttachPresenter(CurrentTimePresenter presenter);
}
CurrentTimePresenter.cs - The Presenter
public class CurrentTimePresenter {
public CurrentTimePresenter(ICurrentTimeView view) {
if (view == null)
throw new ArgumentNullException("view may not be null");
this.view = view;
}
public void InitView(bool isPostBack) {
if (! isPostBack) {
view.CurrentTime = DateTime.Now;
}
}
public void AddDays(string daysUnparsed, bool isPageValid) {
if (isPageValid) {
view.CurrentTime =
DateTime.Now.AddDays(double.Parse(daysUnparsed));
}
else {
view.Message = "Bad inputs...no updated date for you!";
}
}
private ICurrentTimeView view;
}
CurrentTimeView.ascx - The View
The ASCX Page:
...
lblMessage" runat="server">
lblCurrentTime" runat="server">
txtNumberOfDays" runat="server">
txtNumberOfDays" runat="server"
ErrorMessage="Number of days is required" ValidationGroup="AddDays">
txtNumberOfDays" runat="server"
Operator="DataTypeCheck" Type="Double" ValidationGroup="AddDays"
ErrorMessage="Number of days must be numeric">
btnAddDays" Text="Add Days" runat="server"
OnClick="btnAddDays_OnClick" ValidationGroup="AddDays">
...
public partial class Views_CurrentTimeView : UserControl, ICurrentTimeView {
public void AttachPresenter(CurrentTimePresenter presenter) {
if (presenter == null)
throw new ArgumentNullException("presenter may not be null");
this.presenter = presenter;
}
public string Message {
set { lblMessage.Text = value; }
}
public DateTime CurrentTime {
set { lblCurrentTime.Text = value.ToString(); }
}
protected void btnAddDays_OnClick(object sender, EventArgs e) {
if (presenter == null)
throw new FieldAccessException("presenter has" +
" not yet been initialized");
presenter.AddDays(txtNumberOfDays.Text, Page.IsValid);
}
private CurrentTimePresenter presenter;
}
ShowMeTheTime.aspx - The View Initializer
...currentTimeView" runat="server">
...
The ASPX Code-Behind Page:
public partial class ShowMeTheTime : Page // No longer implements ICurrentTimeView
{
protected void Page_Load(object sender, EventArgs e) {
InitCurrentTimeView();
}
private void InitCurrentTimeView() {
CurrentTimePresenter presenter =
new CurrentTimePresenter(currentTimeView);
currentTimeView.AttachPresenter(presenter);
presenter.InitView(Page.IsPostBack);
}
}
C) Presenter InitView
就 象需求中描述的那样,Presenter 应该只在IsPostBack状态下才显示当前时间。需要注意的是Presenter 需要根据是否处于IsPostBack状态来决定做什么。而不应该在ASPX code-behind 中决定。如上面代码所示,ASPX code-behind 并不用判断IsPostBack状态,而仅仅把值传递到Presenter ,由Presenter 决定需要做什么。
这可能会带来问题:“如果另一个control-as-view 产生回传(post-back),该怎么处理呢?”在 这个例子中,当前的时间会保存在Label控件的ViewState中而再次显示在Label控件上,这些都依赖客户的需要。不考虑是否 IsPostBack,其他control-as-view 的初始化过程被传递进来。这样的事情可能常常发生。 这样带来的结果就是View state 的设置有很大的冲突。
在非IsPostBack状态下,如图所示,Presenter 通过view 的接口设置CurrentTime , 序列图纯化论者可能会从图中得到两点暗示:一个是从CurrentTimePresenter 到ICurrentTimeView ,还有一个是从 ICurrentTimeView 到CurrentTimeView.ascx ---其实只有一点即 从 CurrentTimePresenter 到CurrentTimeView.ascx(之间的多态性),中间的接口(中间人)只是为了使 Presenter 不直接依赖于具体的View 。
D) Presenter InitView after IsPostBack
在之前的几个步骤中,用户产生HTTP request,,Presenter 在View上设置设置当前时间,然后通过HTTP response传递给用户。现在用户点击"Add Days" 按钮,并引发一次post-back。在调用Presenter的InitView 之前,所有发生的一切就如前所述。此时,Presenter 对IsPostBack 状态进行检测,并没有向View设置CurrentTime 。
E) Button Click Handled by User Control
ASPX 页的Page_Load 事件发生后,user control的OnClick 事件被触发, View并不自己处理这个事件,而是立即将该事件传递给Presenter,通过查看code-behind of the user control,你将看到 首先确保Presenter是有效的(基于契约设计),然后将命令传递给Presenter进行处理。然后Presenter对页面的有效性进行判断(isPageValid),或设置时间或设置对应错误信息。
上 面对MVP的整个时间处理周期进行了彻底的分析。一旦你掌握了MVP模式,it takes very little time to get all the pieces in place.记着始终要从单元测试开始,并保持以测试驱动开发。单元测试不仅仅是保证MVP的各个部分正常工作,也作为MVP个部分之间通信的规约。(不 理解)一个 基于Visual Studio的关于MVP单元测试的代码片断可以在附录B中找到。下面我们将看一下页面重定向的处理。
III. Page Redirects with MVP & PageMethods
在企业应用开发中,关心的是应用流程(application flow)。谁还会关心页面的重定向呢?重定向的细节应该存储在XML的配置文件中吗?应该用Maverick.NET或Spring.NET 这样的第三方工具来处理页面的流程吗?就个人而言,我喜欢尽可能的使页面重定向与Action接近,换句话说,我觉得把action/redirects 存储在XML的配置文件中不够直接,以至于给理解和维护带来麻烦。另一方面,在ASPX的code-behind 代码中对重定向进行硬编码会使程序变得脆弱(容易出错)和难以解析,而且不是强类型。解决这个问题,可以用PageMethods,可以到http://metasapiens.com/PageMethods免费下载。这样可以使重定向变为强类型的。例如:原来:Response.Redirect("../Project/ShowProjectSummary?projectId=" + projectId.ToString() + "&userId=" + userId.ToString()),用 PageMethods则为:Response.Redirect(MyPageMethods.ShowProjectSummary.ShowSummaryFor(projectId, userId))这样重定向是强类型的,在编译时会进行检查。
一个和MVP模式相关的针对页面重定向的问题:谁负责进行一次重定向,应该怎样发起这次重定向?(对于这个问题,我相信有很多正确的答案,不过我还是要建 议一种更加成功的解决方案)。在Presenter 中对每一个可能的结果增加一个事件,例如,假设一个站点由两个页面组成,第一个页面列出了很多项目,第二个页面通过点击项目名后面的“Edit”进入,允 许用户更新项目名,更新完项目名,用户又将被重定向到项目列表页面。为了达到这个目的,Presenter 应该触发一个事件用来显示项目名已被成功修改,并初始化View ,即ASPX 页面,从而实现对应的重定向。(注意:下面例子只是一个实例,并没有联系到“当前时间”的例子)
Presenter:
...
public event EventHandler ProjectUpdated;
public void UpdateProjectNameWith(string newName) {
...
if (everythingWentSuccessfully) {
ProjectUpdated(this, null);
}
else {
view.Message = "That name already exists. Please provide a new one!";
}
}
...
ASPX Code-Behind:
...
protected void Page_Load(object sender, EventArgs e) {
EditProjectPresenter presenter =
new EditProjectPresenter(editProjectView);
presenter.ProjectUpdated += new EventHandler(HandleProjectUpdated);
presenter.InitView();
}
private void HandleProjectUpdated(object sender, EventArgs e) {
Response.Redirect(MyPageMethods.ShowProjectSummary.Show(projectId, userId));
}
...
这种方法使得页面重定向和Presenter 及View相分离,作为一种经验,Presenter 没有必要引用System.Web,而且,将页面重定向从View (用户控件)中分离出来,用户控件使得View可以被其他的View初始化器,即Aspx页面,复用。这就是通过事件实现基于MVP模型的重定向的最大的好处。
IV. Presentation Security with MVP
常 常,一个列,一个按钮、一个表、或其他什么应该被显示或隐藏是基于浏览站点用户的权限。同样的,一个组件在这个ASPX中是隐藏的,在另外的ASPX中可 见的。其中的安全控制由Presenter 决定,但由View 来实现对应的决定。让我们重新拾起"current time"这个例子,假设客户端希望"Add Days"按钮只在偶数日有效,例如:2、4、6。View 应该将这块区域封装在一个panel中,如下:
...
pnlAddDays" runat="server" visible="false">
txtNumberOfDays" runat="server">
txtNumberOfDays" runat="server"
ErrorMessage="Number of days is required" ValidationGroup="AddDays">
txtNumberOfDays" runat="server"
Operator="DataTypeCheck" Type="Double" ValidationGroup="AddDays"
ErrorMessage="Number of days must be numeric">
btnAddDays" Text="Add Days" runat="server"
OnClick="btnAddDays_OnClick" ValidationGroup="AddDays">
...
注意,panel的可见性被悲观的设为false,虽然在这里隐藏和可见并没有多大区别,但涉及到安全性的元素,可见性设为false会好些。View 的code-behind 代码中将暴露一个setter方法用于隐藏和显示这个panel:
...
public bool EnableAddDaysCapabilities {
set { pnlAddDays.Visible = value; }
}
...
注意,View并没有直接暴露panel,有意这么做有两个原因:1.如果将Panel直接暴露,会导致Presenter 对System.Web的引用,而这正是我们要避免的,2.暴露panel即向Presenter 暴露了View的实现细节。Presenter 越是依赖与View的实现,就越是难在其他View之间复用。As with other OOP scenarios, the pros and cons of exposing implementation details of the View need to be weighed against looser coupling to the Presenter.
最后,在InitView中,Presenter 将检查当前用户是否被允许使用“add-days ”这一个功能,并且在View中设置相应的允许标志。
...
public void InitView() {
view.EnableAddDaysCapabilities = (DateTime.Now.Day % 2 == 0);
}
...
这个示例可以被扩展用以很多其他包含安全检查的场景。注意,这并不是对.NET内建安全机制的替代,而是为可加强更好的控制。
V. Application Architecture with MVP
Download sample MVP Enterprise Solution
最后,怎样在一个数据驱动的企业级应用中使用MVP呢?“企业级应用”,在这里是一个被逻辑上分为多层的应用程序,包括:持久层、领域模型层和数据访问层。下图展示了整个架构:
每个突起的框代表应用程序的特有模块,每个灰色的框则代表一个独立的物理程序集(assembly)。如:MyProject.Web.dll, MyProject.Presenters.dll, MyProject.Core.dll等。箭头表示依赖关系,例如:.Web 程序集依赖于.Presenters 程序集和.Core 程序集。程序集之间通过依赖倒置和依赖注入技术避免双向依赖。我首选的对View 初始器的依赖注入(上图中叫“DI”)的实现,是通过 Castle Windsor项目。数据层采用ORMap框架,NHibernate,来和数据库进行通信。
作为依赖注入的入门,可以看CodeProject的文章:"Dependency Injection for Loose Coupling" (http://www.codeproject.com/csharp/DependencyInjection.asp)。此外,如果想对这种架构有一个全面的了解,(没有Presenters 层和Castle Windsor ),可以看CodeProject的文章:"NHibernate Best Practices with ASP.NET"(http://www.codeproject.com/aspnet/NHibernateBestPractices.asp)这篇文章也描述了如何安装和运行示例程序。
In Summary
乍 一看,实现MVP模式会增加很多额外饿工作量。事实上它可能在开发的初期会减缓开发速度,但是在完成整个开发进度后,你会发现这种模式所带来的好处将远远 大于初期带来的不适。MVP模式将使你的代码更易单元测试,并在整个软件生命周期中保持更好的可扩展性--尤其在维护期时。然而我并不建议你在所有的 ASP.NET应用程序中使用MVP模式。严格的说,MVP模式并不适合于所有情况。一个应用软件的架构应该适合实际的任务而且不应该增加系统的复杂性。 显然,MVP和基于用户控件的MVP是众多选择中两种,如果使用恰当,MVP将使你对创建code-behind的、可测试的、可扩展的应用软件变得更自 信。
附录A:引用
附录B:Visual Studio 2005的MVP单元测试代码片断
在VS 2005的默认安装位置,把下面的内容复制到 "C:\Program Files\Microsoft Visual Studio 8\VC#\Snippets\1033\Visual C#"下的MVP Test Init.snippet。
public void TestInitView() {
$viewInterface$ view = new $mockView$();
$presenter$ presenter = new $presenter$(view);
view.AttachPresenter(presenter);
presenter.InitView();
}
private class $mockView$ : $viewInterface$
{
public void AttachPresenter($presenter$ presenter) {
}
}
$end$]]>
终于把这篇文章翻译完了,感觉真是不容易(E文太poor了)。不过关键是对MVP的理解,翻译中有好多地方不太吃的准,如果有哪位高手发现翻得不对的地方,一定要不吝赐教啊。