您的位置:首页 > 其它

设计模式-- Model View Presenter

2011-05-29 05:25 197 查看
随着UI创建技术(如ASP.NET和Windows®Form)的功能越来越强大,让UI层执行更多功能已成为普遍的做法。由于没有清晰的职责划分,UI层经常成为逻辑层的全能代理,而后者实际上属于应用程序的其他层。ModelViewPresenter(MVP)模式是专门适用于解决此问题的一种设计模式。为了证明我的观点,我将遵循MVP模式为Northwind数据库中的客户创建一个显示屏。

为什么UI层中不应有过多逻辑?如果没有手动运行应用程序,或未能维护自动执行UI组件的高深UI运行程序脚本,则很难测试应用程序UI层中的代码。这本身就是一个麻烦事,而更大的麻烦是应用程序中普通视图间大量的重复代码。当在UI层的不同部分之间复制执行特定业务功能的逻辑时,通常很难发现好的重构候选者。MVP设计模式使得将逻辑和代码从UI层分离更为轻松,从而更易于简化测试可重用代码。

图1显示组成示例应用程序的主要层。请注意UI层和表示层使用不同的软件包。您可能期望它们使用相同的软件包,但实际上一个项目的UI层只应由两种UI元素组成—窗体和控件。在WebForms项目中,通常是ASP.NETWebForms、用户控件和服务器控件的集合。在WindowsForms中,是WindowsForms、用户控件和第三方程序库的集合。此附加层用于分离显示和逻辑。在表示层中可以有实际实现UI行为的对象,如验证显示、UI的集合输入等。



图1应用程序体系结构

遵循MVP

如图2所示,此项目的UI是非常标准的。加载页面时,屏幕将会显示一个填充了Northwind数据库中所有客户的下拉框。如果您从下拉列表中选择一个客户,将会更新页面,以显示该客户的信息。通过遵循MVP设计模式,您可将各种行为从UI层分离,将其置入自身的类中。图3显示一个类图表,表示涉及的不同类之间的关联。



图2客户信息

需要注意的很重要的一点是,表示器并不了解应用程序实际UI层的任何知识。它知道它可以与接口对话,但不知道也不关心接口的具体实现。这就促使了在不同UI技术间表示器的重用。

我将使用测试驱动开发(TDD)来创建客户屏幕功能。图4显示我将使用的第一个测试的详细信息,以说明我期望在页面加载上观察到的行为。TDD使我可以一次将精力集中于一个问题,只编写可使测试通过的足够代码,然后再继续进行。在此测试中,我将利用一个名为NMock2的模拟对象框架来构建接口的模拟实现。



图3MVP类图表

在我的MVP实现中,我决定将表示器作为其将要配合工作的视图的附属。在能使对象立即工作的状态下创建对象总是很好的。在此应用程序中,表示层实际上是依靠服务层来调用域功能的。由于此需求,因此也有必要建立一个带接口的表示器,通过该接口它可以与服务类进行对话。这将确保一旦建立表示器后,它就可以进行所有需要它来完成的工作。我将通过创建两个特定的模拟开始:一个用于服务层,一个用于表示器将要使用的视图。

为什么要创建模拟?单元测试的规则是尽可能的隔离测试,以将精力集中于一个特定的对象。在此测试中,我只关注表示器的预期行为。此时,我并不在意视图接口或服务接口的实际实现,我相信那些接口定义的协议,并相应的设置模拟来表现。这可确保我将测试集中于我所期望的表示器行为,无需考虑其所依赖的对象。调用其初始化方法后,我所期望的表示器行为如下。

首先,表示器应调用ICustomerTask服务层对象上的GetCustomerList方法(在测试中模拟)。请注意您可以使用NMock模仿模拟的行为。而对于服务层,我希望它可将模拟ILookupCollection返回到表示器。然后,在表示器从服务层检索ILookupCollection后,它应调用集合的BindTo方法并将方法传递到ILookupList的实现。通过使用NMockExpect.Once方法,我可以确定如果表示器没有调用该方法一次(且仅一次),则测试将失败。

编写该测试后,我将会处于完全非编辑状态。我将尽可能做最简单的工作来使测试通过。

使第一次测试通过

首先编写测试的好处之一是我现在拥有了一个远景蓝图,可以遵循它来对测试进行编译并最终通过。第一次测试包括两个还不存在的接口。这些接口是正确编译代码的先决条件。我将从IViewCustomerView的代码开始:

publicinterfaceIViewCustomerView
{
ILookupListCustomerList{get;}
}

此接口提供一个属性,该属性可返回一个ILookupList接口实现。对于该问题,我还没有一个ILookupList接口,甚至没有实施工具。为了通过此测试,我不需要明确的实施工具,这样我可以继续创建ILookupList接口:


publicinterfaceILookupList{}

此时,ILookupList接口看起来没什么用处。我的目标是编译并通过测试,而这些接口可以满足测试的需求。现在该将焦点转向我要实际测试的对象-ViewCustomerPresenter了。
此类尚不存在,但回头查看该测试,您可以从中得出两个重要事实:它有一个构造函数,该函数需要视图和服务实现作为依赖,并且有一个空的Initialize方法。图5中的代码显示如何编译测试。

请牢记表示器需要其所有依赖关系,以便富有成效的进行工作;这就是传入视图和服务的原因。我没有实现初始化方法,因此如果运行测试,我将得到NotImplementedException。

如上所述,我没有盲目的编写表示器代码;通过查看测试,我已了解在调用初始化方法后表示器应表现的行为。行为的实现代码如下:


                                            
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: