作者:Billy McCafferty 翻译:张善友
原文地址:http://www.codeproject.com/useritems/ModelViewPresenter.asp
这篇文章描述了ASP.NET 2.0使用Model-View-Presenter 模式实现业务逻辑与表现层的适当分离。
- Download trivial example of MVP - 18 Kb
- Download simple Event-Handling MVP - 19 Kb
- Download sample MVP Enterprise Solution - 2.6 Mb
概述
经过多年代的ASP代码积累,微软开发了具有一流水平的网络平台:ASP.NET. ASP.NET使用后置代码页面方式隔离业务逻辑。虽然用心良苦,但是ASP.NET在企业级应用开发方面还是存在如下的不足:
l 后 置代码页中混合了表现层,业务逻辑层,数据访问层的代码。之所以出现这种情况是因为后置代码充当了事件引发,流程控制,业务规则和表现逻辑,业务逻辑和数 据访问的协调者等多种角色。后置代码页充当这么多的职责导致许多难处理的代码。在企业应用中,一个良好的设计原则是各层之间的适当分离和保持后置代码页内 容的尽可能干净。使用Model-View-Presenter 模式,后置代码的内容将非常简单,严格的管理表现层内容。
l 后置代码模型的另一个缺点是它难以不借助帮助类/工具类实现重用后置代码页面之间的可重用代码。很明显的,这也是提供了一个适当的解决方案,但往往导致ASP式的类,不像是一流的对象。通过适当的设计,每个类都应有清晰的职责,通常一个叫
ContainsDuplicatePresentationCodeBetweenThisAndThat.cs并不合适
l 最后,对后置代码页进行单元测试非常困难因为它们同表现层的太紧密了,当然可以选择NUnitASP这样的工具,但是他们非常的耗费时间,并且难以维护。单元测试应当是简单快速的。
可以采用各种技术手段是后置代码页保持分离。例如Castle MonoRail项目仿效Ruby-On-Rails , 但是放弃了ASP.NET的事件模型。Maverick.NET是一个支持ASP.NET事件模型的框架但是保留后置代码页作为程序的控制器。理想的解决 方案是使用ASP.NET的事件模型并保持后置代码页的尽可能简单。Model-View-Presenter 模式是一个不需要借助第三方框架实现这个目标。
Model-View-Presenter
Model-View-Presenter (MVP) 模式是 Model-View-Controller (MVC) 模式的变种,针对事件模型,像ASP.NET这样的框架。具体参看简单介绍GUI设计模式(MVP)。MVP最初使用与Dolphin Smalltalk. 主要的变化是Presenter实现MVC的Observer设计,基本设计和MVC相同:Model存储数据,View表示Model的表 现,Presenter协调两者之间的通信。在 MVP 中 view 接收到事件,然后会将它们传递到 Presenter, 如何具体处理这些事件,将由 Presenter 来完成。关于在 MVC 和 MVP的深入比较,请查看
http://www.darronschall.com/weblog/archives/000113.cfm.,接下来以三个例子详细说明MVP模式。
最简单的例子
这个例子,客户想在页面上显示当前的时间(从简单的开始容易理解)。显示时间的ASPX页面是“View”。Presenter负责决定现在的时间(Model),而且把Model告知 View。我们从一个单元测试开始。
[TestFixture]
public class CurrentTimePresenterTests {
[Test]
public void TestInitView() {
MockCurrentTimeView view = new MockCurrentTimeView();
CurrentTimePresenter presenter = new CurrentTimePresenter(view);
presenter.InitView();
Assert.IsTrue(view.CurrentTime > DateTime.MinValue);
}
private class MockCurrentTimeView : ICurrentTimeView {
public DateTime CurrentTime {
set { currentTime = value; }
// This getter won't be required by ICurrentTimeView,
// but it allows us to unit test its value.
get { return currentTime; }
}
private DateTime currentTime = DateTime.MinValue;
}
}
上 面的单元测试代码和右边的类图,描述了MVP各个元素之间的关系。单元测试中创建的第一个对象实例是MockCurrentTimeView,从这个单元 测试中可以看出,所有的表现逻辑的单元测试并没有一个ASPX页面(View),所需要的是一个实现视图接口的对象;因此可以创建一个视图的模拟对象 (Mockview)代替真正的视图对象。
下一行代码创建了一个Presenter的对象实例,通过它的构造函数传递了一个实现ICurrentTimeView接口的对象,这样,Presenter现在能够操作View,从类图中可以看出,Presenter只与View的接口通信。这允许实现相同的View接口的多个View被Presenter使用。
最后,Presenter调用InitView()方法,这个方法将获取当前的时间并通过公开的属性ICurrentTimeView传递给视图(View),单元测试断言CurrentTime的值应比它的初始值大(如果需要可以做更多的断言)。
那么现在要做的就是要运行单位测试并通过了!
ICurrentTimeView.cs – 视图接口
使单元测试编译通过的第一步是创建ICurrentTimeView.cs,这个接口提供Presenter 和 View之间的沟通桥梁,在这个例子中,视图接口需要暴露一个Model数据,使Persenter能够将Model(当前时间)传递给View。
public interface ICurrentTimeView {
DateTime CurrentTime { set; }
}
因为只需要显示模型数据,视图接口中只需要一个CuttentTime的Set;但是设置了一个Get,用于在单元测试中获取视图的CurrentTime,它也可以添加到MockCurrentTimeView而不要在接口中定义,这样,在视图接口中暴露的接口属性不需要定义getter/setter(上面的单元测试就使用了这个技术)。
CurrentTimePresenter.cs - The Presenter
Presenter处理同Model之间的逻辑并将Model传递给View。要使单元测试通过编译,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() {
view.CurrentTime = DateTime.Now;
}
private ICurrentTimeView view;
}
完成上述代码,我们就完成了Unit Test,mock view,Presenter和View.单元测试现在可以成功编译并通过。下一个步骤是创建ASPX页面充当真正的View。
注意到ArgumentNullException异常的检查,这项技术被称为基于契约设计(Design By Contract),在代码中象这样做必要的检查可以大大的降低Bug的数量。关于基于契约设计(Design By Contract)的更多信息请参考http://archive.eiffel.com/doc/manuals/technology/contract和http://www.codeproject.com/csharp/designbycontract.asp.
ShowMeTheTime.aspx - The View
这个页面需要做以下内容:
l ASPX页面需要提供一个方法显示当前的时间,用一个Label控件显示时间
l 后置代码必须实现接口IcurrentTimeView
l 后置代码必须创建一个Presenter对象,并把自己传递给它的构造函数
l 创建好Persenter对象后,需要调用InitView()
ASPX 页面:
<asp:Label >另一个用户控件引发的Post-back对这个用户控件的影响。即使你没有使用MVP,也是一个好问题。