设计模式-- Model View Presenter

 

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

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

 

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

图 1 应用程序体系结构

遵循 MVP

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

图 2 客户信息

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

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

图 3 MVP 类图表

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

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

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

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

使第一次测试通过

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

public interface IViewCustomerView
{
ILookupList CustomerList { get; }
}

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


public interface ILookupList { }

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

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

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

 

你可能感兴趣的:(Microsoft,技术,软件架构,设计模式,测试,ui,forms,asp.net,interface)