MVC、MVP以及Model2[下篇]2012-10-13 博客园 Artech[上篇]通过采用MVC模式,我们可以将可视化UI元素的呈现、UI处理逻辑和业务逻辑分别定义在View、Controller和Model中,但是对于三者之间的交互,MVC并没有进行严格的限制。最为典型的就是允许View和Model绕开Controller进行直接交互,View不仅仅可以通过调用Model获取需要呈现给用户的数据,Model也可以直接通知View让其感知到状态的变化。当真正地将MVC应用于具体的项目开发中,不论是基于GUI的桌面应用还是基于Web UI的Web应用,如果不对Model、View和Controller之间的交互进行更为严格的限制,我们编写的程序可以比自治视图更为难以维护。今天我们将MVC视为一种模式(Pattern),但是作为MVC最初提出者的Trygve M. H. Reenskau实际是将MVC视为一种范例(Paradigm),这可以从它在《Applications Programming in Smalltalk-80(TM): How to use Model-View-Controller (MVC)》中对MVC的描述可以看出来:In the MVC paradigm the user input, the modeling of the external world, and the visual feedback to the user are explicitly separated and handled by three types of object, each specialized for its task.模式和范例的区别在于前者可以直接应用到具体的应用上,而后者则仅仅提供一些基本的指导方针。在我看来MVC是一个很宽泛的概念,任何基于Model、View和Controller对UI应用进行分解的设计都可以成为MVC。当我们采用MVC的思想来设计UI应用的时候,应该根据应用框架(比如Windows Forms、WPF和Web Forms)的特点对Model、View和Controller的界限以及相互之间的交互设置一个更为严格的规则。在软件设计的发展历程中出现了一些MVC的变体(Varation),它们遵循定义在MVC中的基本原理。我们现在来简单地讨论一些一个常用的MVC变体。一、MVPMVP是一种广泛使用的基于架构模式,使用与基于事件驱动的应用框架,比如ASP.NET Web Forms和Windows Forms应用。MVP中的M和V对应中MVC的Model和View,而P(Presenter)则自然代替了MVC中的Controller。但是MVP并非仅仅体现在从Controller到Presenter的转换,更对地体现在Model、View和Presenter之间的交互上。MVC模式中元素之间混乱的交互只要体现在允许View和Model绕开Controller进行单独“交流”,这在MVP模式中得到了彻底地解决。如下图所示,能够与Model直接进行交互的仅限于Presenter,View只能间接地通过Presenter调用Model。Model的独立性在这里得到了真正的体现,它不仅仅与可视化元素的呈现无关(View)和与UI处理逻辑(Presenter)无关。使用MVP的应用是用户驱动的而非Model驱动的,所以Model不需要主动通知View以提醒状态发生了改变。

MVP不仅仅避免了View和Model之间的耦合,更进一步地降低Presenter对View的依赖。如图1-2所示,Presenter依赖的是一个抽象化的View,即View实现的接口IView。这带来的最直接的好处就是使定义在Presenter中的UI处理逻辑变得易于测试。由于Presenter对View的依赖行为定义在接口IView中,我们只需要Mock一个实现了该接口的View就能对Presenter进行测试。构成MVP三要素之间的交互体现在两个方面,即View/Presenter和Presenter/Model。Presenter和Model之间的交互很清晰,仅仅体现在Presenter对Model的单向调用。而View和Presenter之间该采用怎样的交互方式是整个MVP的核心,MVP针对关注点分离的初衷能否体现在具体的应用中很大程度上取决于两者之间的交互方式是否正确。按照View和Presenter之间的交互方式以及View本身的职责范围,Martin Folwer将MVP可分为PV(Passive View)和SoC(Supervising Controller)两种模式。PV与SoC解决View难以测试的最好的办法就是让它无须测试,如果View不需要测试,其先决条件就是让它尽可能不涉及到UI处理逻辑,而这就是PV模式目的所在。顾名思义,PV(Passive View)是一个被动的View,针对包含其中的UI元素(比如控件)的操作不是由View自身来操作,而交给Presenter来操控。如果我们纯粹地采用PV模式来设计View,意味着我们需要将定义View中的UI元素通过属性的形式暴露出来。具体来说,当我们在为View定义接口的时候,需要定义基于UI元素的属性以使Presenter可以对View进行细粒度地操作,但这并不是意味着我们直接将View上的控件暴露出来。举个简单的例子,我们开发的HR系统 中具有如下图所示的Web页面用于根据部门获取员工列表。

现在通过ASP.NET Web Form应用来涉及这个页面,我们来讨论一下如果采用PV模式View的接口该如何定义。对于Presenter来说,View供它操作的控件有两个,一个是包含所有部门列表的DropDownList,另一个则是显示员工列表的GridView。在页面加载的时候,Presenter将部门列表绑定在DropDownList上,与此同时包含所有员工的列表被绑定到GridView。当用户选择某个部门并点击“查询”按钮后,View将包含筛选部门在内的查询请求转发给Presenter,后者筛选出相应的员工列表之后将其绑定到GridView。如果我们为该View定义一个接口IEmployeeSearchView,我们不能像如下的代码所示将上述这两个控件直接以属性的形式暴露出来。针对数据绑定对控件类型的选择属于View的内部细节(比如说针对部门列表的显示,我们可以选择DropDownList也可以选择ListBox),不能体现在表示用于抽象View的接口中。在另一方面,理想情况下定义在Presenter中的UI处理逻辑应该是与具体的技术平台无关的,如果在接口中涉及到了控件类型,这无疑将Presenter也具体的技术平台绑定在了一起。
 1: public interface IEmployeeSearchView
 2: {
 3: DropDownList Departments { get;}
 4: GridView Employees { get; }
 5: }
正确的接口和实现该接口的View(一个Web页面)应该采用如下的定义方式。Presenter通过属性Departments和Employees进行赋值进而实现对DropDownList和GridView进行绑定,通过属性SelectedDepartment得到用户选择的筛选部门。为了尽可能让接口只暴露必须的信息,我们特意将对属性的读写作了控制。
 1: public interface IEmployeeSearchView
 2: {
 3: IEnumerable<string> Departments { set; }
 4: string SelectedDepartment { get; }
 5: IEnumerable<Employee> Employees { set; }
 6: }
 7: 
 8: public partial class EmployeeSearchView: Page, IEmployeeSearchView
 9: {
10: //其他成员
11: public IEnumerable<string> Departments
12: {
13: set
14: {
15: this.DropDownListDepartments.DataSource = value;
16: this.DropDownListDepartments.DataBind();
17: }
18: }
19: public string SelectedDepartment
20: {
21: get { return this.DropDownListDepartments.SelectedValue;}
22: }
23: public IEnumerable<Employee> Employees
24: {
25: set
26: {
27: this.GridViewEmployees.DataSource = value;
28: this.GridViewEmployees.DataBind();
29: }
30: }
31: }