MVP设计模式

维基百科前端

Model-view-presenter (MVP)使用者接口设计模式的一种,被广范用于便捷自动化单元测试和在呈现逻辑中改良分离关注点(separation of concerns)。web

  • Model 定义使用者接口所须要被显示的资料模型,一个模型包含着相关的商业逻辑。
  • View 视图为呈现使用者接口的终端,用以表现来自 Model 的资料,和使用者命令路由再通过 Presenter 对事件处理后的资料。
  • Presenter 包含着元件的事件处理,负责检索 Model 取得资料,和将取得的资料通过格式转换与 View 进行沟通。

MVP 设计模式一般会再加上 Controller 作为总体应用程序的后端程序工做。数据库

百度百科后端

mvp 的全称为Model-View-Presenter,Model提供数据,View负责显示,Controller/Presenter负责逻辑的处理。 MVP与MVC有着一个重大的区别:在MVP中View并不直接使用Model,它们之间的通讯是经过Presenter (MVC中的Controller)来进行的,全部的交互都发生在Presenter内部,而在MVC中View会直接从Model中读取数据而不是经过 Controller。设计模式

1 MVC和MVP 服务器

MVP 是从经典的模式MVC演变而来,它们的基本思想有相通的地方:Controller/Presenter负责逻辑的处理,Model提供数据,View负 责显示。做为一种新的模式,MVP与MVC有着一个重大的区别:在MVP中View并不直接使用Model,它们之间的通讯是经过Presenter (MVC中的Controller)来进行的,全部的交互都发生在Presenter内部,而在MVC中View会从直接Model中读取数据而不是经过 Controller。框架

在MVC里,View是能够直接访问Model的!从而,View里会包含Model信息,不可避免的还要包括一些 业务逻辑。 在MVC模型里,更关注的Model的不变,而同时有多个对Model的不一样显示,及View。因此,在MVC模型里,Model不依赖于View,可是 View是依赖于Model的。不只如此,由于有一些业务逻辑在View里实现了,致使要更改View也是比较困难的,至少那些业务逻辑是没法重用的。[1] ide

2解决MVC问题函数

在 MVP里,Presenter彻底把Model和View进行了分离,主要的程序逻辑在Presenter里实现。并且,Presenter与具体的 View是没有直接关联的,而是经过定义好的接口进行交互,从而使得在变动View时候能够保持Presenter的不变,即重用! 不只如此,咱们还能够编写测试用的View,模拟用户的各类操做,从而实现对Presenter的测试--而不须要使用自动化的测试工具。 咱们甚至能够在Model和View都没有完成时候,就能够经过编写Mock Object(即实现了Model和View的接口,但没有具体的内容的)来测试Presenter的逻辑。 在MVP里,应用程序的逻辑主要在Presenter来实现,其中的View是很薄的一层。所以就有人提出了Presenter First的设计模式,就是根据User Story来首先设计和开发Presenter。在这个过程当中,View是很简单的,可以把信息显示清楚就能够了。在后面,根据须要再随便更改View, 而对Presenter没有任何的影响了。 若是要实现的UI比较复杂,并且相关的显示逻辑还跟Model有关系,就能够在View和Presenter之间放置一个Adapter。由这个 Adapter来访问Model和View,避免二者之间的关联。而同时,由于Adapter实现了View的接口,从而能够保证与Presenter之 间接口的不变。这样就能够保证View和Presenter之间接口的简洁,又不失去UI的灵活性。 在MVP模式里,View只应该有简单的Set/Get的方法,用户输入和设置界面显示的内容,除此就不该该有更多的内容,毫不允许直接访问 Model--这就是与MVC很大的不一样之处。[1] 工具

3优势

一、模型与视图彻底分离,咱们能够修改视图而不影响模型

二、能够更高效地使用模型,由于全部的交互都发生在一个地方——Presenter内部

三、咱们能够将一个Presenter用于多个视图,而不须要改变Presenter的逻辑。这个特性很是的有用,由于视图的变化老是比模型的变化频繁。

四、若是咱们把逻辑放在Presenter中,那么咱们就能够脱离用户接口来测试这些逻辑(单元测试)[1] 

4缺点

因为对视图的渲染放在了Presenter中,因此视图和Presenter的交互会过于频繁。还有一点须要明白,若是Presenter过多地渲染了视 图,每每会使得它与特定的视图的联系过于紧密。一旦视图须要变动,那么Presenter也须要变动了。好比说,本来用来呈现Html的 Presenter如今也须要用于呈现Pdf了,那么视图颇有可能也须要变动。

 

微软文档

设计模式:Model View Presenter

Jean-Paul Boodhoo

 

随 着 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。

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

public void Initialize()
{            
task.GetCustomerList().BindTo(view.CustomerList);
}

本文附带的源代码中有 CustomerTask 类(实现了 ICustomerTask 接口)中 GetCustomerList 方法的完整实现。虽然从实现和测试表示器的角度看,我还无需了解是否存在工做实现。但正是该抽象级别使我难以经过表示器类的测试。第一个测试如今正处于将 要编译和运行的状态。这证实在调用表示器上的 Initialize 方法时,它将以我在测试中指定的方式与其依赖对象进行交互,而且最终当这些依赖对象的具体实现被插入表示器时,我能够确信结果视图(ASPX 页)将被客户列表所填充。

填充 DropDownList

到 目前为止,我主要处理了接口,抛开实际的实现细节,将精力集中于表示器。如今,该创建一些探测代码了,它最终将容许表示器以一种可测试的方式在 Web 页面上填充列表。实现此功能的关键是将在 LookupCollection 类的 BindTo 方法中发生的交互。若是您看一下图 6 中 LookupCollection 类的实现,就会注意到它实现了 ILookupCollection 接口。本文的源代码带有随附测试,可用于创建 LookupCollection 类的功能。

BindTo 方法的实现特别有趣。请注意在此方法中,集合将重复 ILookupDTO 实现自己的私有列表。ILookupDTO 是一个接口,可很好地与 UI 层的组合框绑定:

public interface ILookupDTO
{
string Value { get; }   
string Text { get; }
}

图 7 显示用于测试查找集合的 BindTo 方法的代码,此方法将会帮助解释 LookupCollection 与 ILookupList 之间的预期交互。最后一点特别有趣。在此测试中,我但愿在尝试向列表添加项目前,LookupCollection 将会调用 ILookupList 实现中的 Clear 方法。而后,我但愿能够在 ILookupList 上调用 Add 10 次,而做为 Add 方法的参数,LookupCollection 将在实现 ILookupDTO 接口的对象中传递。若要使其与 Web 项目中的控件(例以下拉列表框)配合使用,则您须要建立一个 ILookupList 实现,该实现知道如何与 Web 项目中的控件配合使用。

本 文附带的源代码包含一个名为 MVP.Web.Controls 的项目。该项目包含我选择用于建立完整解决方案的全部 Web 特定控件或类。为何我将代码放在此项目中,而不是放在 APP_CODE 目录或 Web 项目中?回答是可测试性。在没有手动运行应用程序或没有使用某种测试程序自动执行 UI 测试的状况下,很难直接测试 Web 项目中的任何控件。MVP 模式使我可在没必要手动运行应用程序的状况下考虑更高的抽象级别,并测试核心接口(ILookupList 和 ILookupCollection)的实现。我打算向 Web.Controls 项目中添加一个新类:WebLookupList 控件。图 8 显示此类的第一次测试。

某些事项在图 8 所示的测试中比较突出。显然,测试项目须要一个到 System.Web 库的引用,这样它就能够实例化 DropDownList Web 控件。进一步查看测试,您应了解 WebLookupList 类将会实现 ILookupList 接口。它还会将 ListControl 做为一个依赖对象。System.Web.UI.WebControls 命名空间中两个最多见的 ListControl 实现是 DropDownList 和 ListBox 类。图 8 中测试的主要功能是要确保 WebLookupList 正确的将实际 Web ListControl 的状态更新为其正在委派责任的状态。图 9 显示 WebLookupList 实现中涉及的类的类图表。我能够经过图 10 中的代码,知足对 WebLookupList 控件第一次测试的要求。

 

图 9 WebLookupList 类

请 记住,MVP 的一个关键是由建立视图接口引入的层的分离。表示器不了解视图的具体实现,以及它要对话的各个 ILookupList,它只知道它能够调用这些接口定义的任何方法。最后,WebLookupList 类是一个包装并委托至底层 ListControl 的类(在 System.Web.UI.WebControls 项目中定义的某些 ListControls 的基类)。利用这些代码,我能够编译并运行 WebLookupList 控件测试,如今测试应该顺利经过了。我能够为 WebLookupList 再添加一个测试,以测试 Clear 方法的实际行为:

[Test]
public void ShouldClearUnderlyingList()
{
ListControl webList = new DropDownList();
ILookupList list = new WebLookupList(webList);
    
webList.Items.Add(new ListItem("1", "1"));
    
list.Clear();
    
Assert.AreEqual(0, webList.Items.Count);
}

另外,我将测试在调用 WebLookupList 类自身的方法时,它是否会真正更改底层 ListControl (DropDownList) 的状态。WebLookupList 如今能够完成填充 Web Form 中 DropDownList 的功能。如今可将全部程序绑定在一块儿,就可得到已填充客户列表的 Web 页面下拉列表。

实现视图接口

由 于我在创建 Web Form 前端,所以 IViewCustomerView 接口的实现程序必须是 Web Form 或用户控件。出于此列的缘由,我将其设为 Web Form。页面的常规外观已经建立,如图 2 所示。如今我只须要实现视图接口。切换到 ViewCustomers.aspx 页的源代码,我能够添加如下代码,表示须要此页来实现 IViewCustomersView 接口:

public partial class ViewCustomers :Page,IViewCustomerView

若是观察示例代码,您将会发现 Web 项目和 Presentation 是两个彻底不一样的程序集。并且,Presentation 项目没有引用任何 Web.UI 项目,这样可进一步维护分离层。另外一方面,Web.UI 项目必须引用 Presentation 项目,由于视图接口和表示器都位于该项目中。

经过选择实现 IViewCustomerView 接口,如今咱们的 Web 页面能够实现由该接口定义的任何方法或属性。当前 IViewCustomerView 接口上只有一个属性,是一个可返回 ILookupList 接口任何实现的 getter。我已向 Web.Controls 项目中添加了引用,这样就能够实例化 WebLookupListControl。我这样作是由于 WebLookupListControl 实现了 ILookupList 接口,而且它知道如何委托给 ASP.NET 中的实际 WebControls。请查看 ViewCustomer 页面的 ASPX,您将会发现客户列表只是一个 asp:DropDownList 控件:

<td>Customers:</td>
<td><asp:DropDownList id="customerDropDownList" AutoPostBack="true" 
runat="server" Width="308px"></asp:DropDownList></td>
</tr>

利用这些已有代码,我能够快速的继续实现知足 IViewCustomerView 接口实现所需的代码:

public ILookupList CustomerList
{
get { return new WebLookupList(this.customerDropDownList);}
}

我如今须要调用表示器上的 Initialize 方法,以触发该方法实际执行一些操做。所以,视图须要可以实例化表示器,这样就能够调用它的方法了。若是回头查看一下表示器,您会记得它须要视图和服务与 之配合使用。ICustomerTask 接口表示位于应用程序服务层的接口。服务层一般负责协调域对象之间的交互,并将这些交互的结果转换为“数据传输对象”(Data Transfer Objects, DTO),而后将其从服务层传递到表示层,再到 UI 层。可是此处有一个问题:我已规定表示器须要与视图和服务实现一同构造。

表示器的实际实例化将在 Web 页的源代码中进行。这是一个问题,由于 UI 项目没有引用任何服务层项目。可是,表示项目却引用了服务层项目。经过将一个重载构造函数添加到 ViewCustomerPresenterClass 中,能够解决此问题:

public ViewCustomerPresenter(IViewCustomerView view) : 
this(view, new CustomerTask()) {}

这一新的构造函数同时知足了表示器视图和服务的实现要求,同时还可从服务层维护 UI 层的分离。如今完成源代码的后续代码就很简单了:

protected override void OnInit(EventArgs e)
{
base.OnInit(e);
presenter = new ViewCustomerPresenter(this);
}
 
protected void Page_Load(object sender, EventArgs e)
{        
if (!IsPostBack) presenter.Initialize();
}

请注意,表示器实例化的关键是:我将利用新建的构造函数重载,而且 Web Form 会将其自身做为实现视图接口的对象传入。

利用实现的源代码中的代码,我能够当即建立并运行应用程序。如今不须要源代码中的任何数据绑定代码,就可使用客户名称列表来填充 Web 页上的 DropDownList。另外,已在最终一块儿工做的全部代码段上运行了测试分数,这可确保表示层体系结构将按预期运转。

如今我准备展现一下在 DropDownList 中显示选定客户信息所需的步骤,以此来总结我对 MVP 的讨论。再次重申,我将首先编写一个测试,来描述我所但愿观察到的行为。(请参阅图 11)。

如 上所述,我将利用 NMock 程序库来建立任务和视图接口的模拟。此特定测试将经过向服务层请求表示特定客户的 DTO 来验证表示器的行为。表示器从服务层检索到 DTO 后,它将直接更新视图上的属性,这样视图就没必要了解任何有关如何正确显示对象信息的知识。简便起见,我将再也不讨论 WebLookupList 控件上 SelectedItem 属性的实现;相反,我会将它留给您去检查源代码,以了解实现的详细信息。此测试真正展现的是在表示器从服务层检索 CustomerDTO 后,表示器和视图之间发生的交互。若是如今尝试运行测试,我将面临一个严重的失败,由于视图接口上的许多属性都还不存在。所以,我将继续进行并为 IViewCustomerView 接口添加必要的成员,如图 12 所示。

这 些接口成员添加完成以后,个人 Web Form 也许会抱怨,由于它再也不知足接口协议了,因此我必须返回 Web Form 的源代码并实现其他的成员。如上所述,Web 页的整个标记已经建立,同时表格单元格已被标记为 "runat=server" 属性,而且已根据其应显示的信息进行了命名。这样就可使结果代码很是轻松的实现接口成员:

public string CompanyName
{
set { this.companyNameLabel.InnerText = value; }
}
public string ContactName
{
set { this.contactNameLabel.InnerText = value; }
}
...

随着 setter 属性的实现,如今只剩下最后一件事要完成。我须要一种方法来告诉表示器显示选定客户的信息。回头看看测试,您会发现此行为的实现位于表示器的 DisplayCustomerDetails 方法中。可是,此方法不带有任何参数。调用时,表示器将返回视图,从中提取其所需的任何信息(使用 ILookupList 检索),而后使用该信息检索选定客户的详细信息。从 UI 角度看,我须要作的就是将 DropDownList 的 AutoPostBack 属性设置为 true,我还须要将如下事件处理程序挂钩代码添加到页面的 OnInit 方法中:

protected override void OnInit(EventArgs e)
{
base.OnInit(e);
presenter = new ViewCustomerPresenter(this);
this.customerDropDownList.SelectedIndexChanged += delegate
    {
presenter.DisplayCustomerDetails();
    };
}

此事件处理程序可确保在下拉列表中选择新客户时,视图将请求表示器显示该客户的详细信息。

重要的是注意这是典型行为。当视图请求表示器执行操做时,它不会给予任何特定的详细信息,而且将由表示器来决定是否返回视图,并使用视图接口来获取其所需的任何信息。图 13 显示实现表示器中所需行为的代码。

但愿您如今能够了解添加表示器层的价值了。表示器负责尝试检索须要显示其详细信息的客户 ID。这就是一般在源代码中执行的代码,可是它如今位于类中,我能够在任何表示层技术之外对其进行彻底的测试和实践。

如 果表示器可以从视图中检索有效的客户 ID,则它将转向服务层并请求表示该客户详细信息的 DTO。表示器得到 DTO 后,它将使用 DTO 中包含的信息更新视图。要注意的关键一点是视图接口的简单性,除 ILookupList 接口之外,视图接口彻底由字符串 DataTypes 组成。表示器的最终职责是正确地转换和格式化从 DTO 中检索的信息,这样它就能够做为字符串,实际被传递到视图。虽然未在此例中说明,但表示器还可负责从视图中读取信息,并将其转换为服务层所期待的必要类 型。

完成全部代码段后,我如今就能够运行应用程序了。首次加载页面时,我会得到一个客户列表,而且在 DropDownList 中显示(未选中)第一个客户。若是我选择一个客户,则会出现回发,视图与表示器之间发生交互,而且会使用相关的客户信息更新 Web 页面。

相关文章
相关标签/搜索