1. 问题
许多计算机系统的用途都是从数据存储检索数据并将其显示给用户。在用户更改数据之后,系统再将更新内容存储到数据存储中。因为关键的信息流发生在数据存储和用户界面之间,所以您可能倾向于将这两部分绑在一起,以减少编码量并提高应用程序性能。但是,这种看起来自然而然的方法有一些问题。一个问题是,用户界面的更改往往比数据存储系统的更改频繁得多。将数据和用户界面这两部分耦合在一起带来的另一个问题是,业务应用程序往往会并入远不止数据传输功能的其他业务逻辑。
那么如何让Web应用程序的用户界面功能实现模块化,以便您可以轻松地单独修改各个部分?
2. 分析
2.1 影响因素
下列影响因素作用于此上下文内的系统,在考虑问题的解决方案时必须协调这些因素:
用户界面逻辑的更改往往比业务逻辑频繁,尤其是在基于 Web 的应用程序中。例如,可能添加新的用户界面页,或者可能完全打乱现有的页面布局。毕竟,基于 Web 的瘦客户端应用程序的优点之一是可以随时更改用户界面,而不必重新分发应用程序。如果将显示代码和业务逻辑组合一起并放在单个对象中,则每次更改用户界面时,都必须修改包含业务逻辑的对象。这很可能引入错误,并需要在对用户界面进行每个极小更改之后都要重新测试所有业务逻辑。
在某些情况下,应用程序以不同的方式显示同一数据。例如,分析员喜欢用电子表格视图显示数据,而管理人员喜欢用饼图显示相同的数据。在一些胖客户端用户界面中,常常用多个视图同时显示相同数据。如果用户在一个视图中更改了数据,则系统必须自动更新该数据的其他所有视图。
设计令人赏心悦目而有效的 HTML 页通常要求采用一套与开发复杂业务逻辑不同的技能。很少有人同时具有这两种技能。因此,将这两部分的开发工作分隔开来是最理想的。
在 Web 应用程序中,单个页面请求将这两方面的工作组合在一起:与用户所选链接相关联的操作进行的处理,以及目标页面的显示。在许多情况下,目标页可能不与操作直接相关。例如,假设有一个用于显示项目列表的简单 Web 应用程序。在将项目添加到列表或从列表中删除项目之后,用户将返回主列表页。因此,应用程序必须在执行两个有很大差异的命令(添加或删除)之后显示相同页面(列表),而所有这些操作均在同一个 HTTP 请求内进行。
与业务逻辑相比,用户界面代码对设备的依赖性往往更大。如果要将应用程序从基于浏览器的应用程序迁移到个人数字助理 (PDA) 或支持 Web 的手机上,则必须替换很多用户界面代码,而业务逻辑可能不受影响。这两部分的完全分离可以使迁移更快完成,并最大限度地降低将错误引入业务逻辑的风险。
通常,为用户界面创建自动测试比为业务逻辑更难、更耗时。因此,减少直接绑到用户界面中的代码量可提高应用程序的可测试性。
3. 解决方案
MVC(Model-View-Controller)模式基于用户输入将域的建模、显示和操作分为三个独立的类:
- 模型。模型用于管理应用程序域的行为和数据,并响应为获取其状态信息(通常来自视图)而发出的请求,还会响应更改状态的指令(通常来自控制器)。
- 视图。视图用于管理信息的显示。
- 控制器。控制器用于解释用户的鼠标和键盘输入,以通知模型和/或视图进行相应的更改。
下图描述了这三个对象之间的结构关系。
请务必注意,视图和控制器都依赖于模型。但是,模型既不依赖于视图,也不依赖于控制器。这是分离的主要优点之一。这样的分离允许模型在独立于可视表示功能的情况下建立和测试。在许多胖客户端应用程序中,视图与控制器的分离是次要的,实际上,许多用户界面框架将角色实现为一个对象。另一方面,在 Web 应用程序中,视图(浏览器)与控制器(处理 HTTP 请求的服务器端组件)的分离是很好定义的。
3.1 变型
在 Application Programming in Smalltalk-80: How to use Model-View-Controller (MVC)中,Steve Burbeck 描述了 MVC 的两个变型:被动模型和主动模型。
当一个控制器以独占方式操作模型时,将使用被动模型。控制器将修改模型,然后通知视图:模型已经更改,应该进行刷新。此情况下的模型完全独立于视图和控制器,这意味着模型无法报告其状态更改。HTTP 协议是此方案的示例。浏览器没有从服务器获取异步更新的简单方法。浏览器显示视图并对用户输入作出响应,但是它不会检测服务器上的数据更改。仅当用户显式请求刷新时,才会询问服务器是否发生了更改。
当模型更改状态而不涉及控制器时,将使用主动模型。当其他资源正在更改数据并且更改必须反映在视图中时,可能会发生这种情况。以股票报价机的显示为例。您从外部源接收股票数据,并希望当股票数据更改时更新视图(例如,报价机数据区和警告窗口)。因为只有模型检测对其内部状态的更改(在这些更改发生时),所以模型必须通知视图刷新显示。
但是,使用 MVC 模式的一个目的是使模型独立于视图。如果模型必须将更改通知视图,则会重新带来您希望避免的依赖性。幸运的是,Observer 模式提供了这样的机制:提醒其他对象注意状态的更改,而不会导致对这些对象的依赖性。各个视图实现 Observer 接口,并向模型注册。模型将跟踪由订阅更改的所有观察器组成的列表。当模型发生改变时,模型将会遍历所有已注册的观察器,并将更改通知它们。此方法通常称为"发布-订阅"。模型从不需要有关任何视图的特定信息。实际上,在需要将模型更改通知控制器的情况下(例如,启用或禁用菜单选项),控制器必须做的全部工作是实现 Observer 接口并订阅模型更改。对于存在许多视图的情况,定义多个主体是有意义的,其中每个主体都描述了特定类型的模型更改。然后,每个视图都只能订阅与视图有关的更改类型。
下图显示了使用 Observer 的主动 MVC 的结构,以及Observer如何将模型与直接引用视图隔离开来。
下图说明当模型发生改变时 Observer 如何通知视图。
ASP.NET提供了一个很好的实现这种经典设计模式的类似环境。开发者通过在ASPX页面中开发用户接口来实现视图;控制器的功能在逻辑功能代码(通常为CodeBehind文件)中实现;模型通常对应应用系统的业务部分。在ASP.NET中实现这种设计而提供的一个多层系统,较经典的ASP结构实现的系统来说有明显的优点。将用户显示(视图)从动作(控制器)中分离出来,提高了代码的重用性。将数据(模型)从对其操作的动作(控制器)分离出来可以让你设计一个与后台存储数据无关的系统。就MVC结构的本质而言,它是一种解决耦合系统问题的方法。
4.1 视图
视图是模型的表示,它提供用户交互界面。使用多个包含单显示页面的用户部件,复杂的Web页面可以展示来自多个数据源的内容,并且网页人员、美工能独自参与这些Web页面的开发和维护。
在ASP.NET下,视图的实现很简单。每一个页面都可以采用复合视图的形式即:一个页面由多个子视图组成;子视图可以是最简单HTML 控件、服务器控件或多个控件嵌套构而成的Web自定义控件。页面都由模板定义,模板定义了页面的布局,用户控件的标签和数目,用户指定一个模板,平台根据这些信息自动创建页面。针对静态的模板内容,如页面上的站点导航,菜单,友好链接,这些使用缺省的模板内容配置;针对动态的模板内容(主要是业务内容),由于用户的请求不同,只能使用后期绑定,并且针对用户的不同,用户部件的显示内容进行过滤。使用由用户部件根据模板配置组成的组合页面,它增强了可重用性,并原型化了站点的布局。
视图部分大致处理流程如下:首先,页面模板定义了页面的布局;页面配置文件定义视图标签的具体内容(用户控件);然后,由页面布局策略类初始化并加载页面;每个用户部件根据它自己的配置进行初始化,加载校验器并设置参数,以及事件的委托等;用户提交后,通过了表示层的校验,用户控件把数据自动提交给业务实体即模型。
4.2 控制器
为了能够控制和协调每个用户跨越多个请求的处理,控制机制应该以集中的方式进行管理。因此,为了达到集中管理的目的引入了控制器。应用程序的控制器集中从客户端接收请求(典型情况下是一个运行浏览器的用户),决定执行何种业务逻辑功能,然后将产生下一步用户界面的责任委派给一个适当的视图组件。
用控制器提供一个控制和处理请求的集中入口点,它负责接收、截取并处理用户请求;并将请求委托给分发者类,根据当前状态和业务操作的结果决定向客户呈现的视图。
为了使请求捕获者类自动捕获用户请求并进行处理,ASP.NET 提供低级别的请求/响应 API,使开发人员能够使用 .NET 框架类为传入的 HTTP 请求提供服务。
4.3 模型
MVC系统中的模型从概念上可以分为两类――系统的内部状态和改变系统状态的动作。模型是所有的业务逻辑代码片段所在。业务处理对象封装了具体的处理逻辑,调用业务逻辑模型,并且把响应提交到合适的视图组件以产生响应。业务实体对象可以通过定义属性描述客户端表单数据。通过业务实体对象实现了对视图和模型之间交互的支持。实现时把"做什么"(业务处理)和"如何做"(业务实体)分离。这样可以实现业务逻辑的重用。由于各个应用的具体业务是不同的,这里不再列举其具体代码实例。
5. MVC小结
围绕 MVC 模式构建显示层具有下列优缺点:
优点
支持多个视图。因为视图与模型分离,而且模型与视图之间没有直接依赖性,所以用户界面可以同时显示同一数据的多个视图。例如,Web 应用程序中的多个页面可以使用同一模型对象。另一个示例是允许用户对页面外观进行更改的 Web 应用程序。这些页面显示来自共享模型的同一数据,但以不同的方式进行显示。
适应更改。用户界面要求的更改往往比业务规则快。用户可能更喜欢新设备(如手机或 PDA)采用另一颜色、字体、屏幕布局和支持级别。因为模型不依赖于视图,所以将新类型的视图添加到系统中通常不会影响模型。因此,更改的作用范围仅限于视图。此模式为其进一步的专门化模式(如 Page Controller 和Front Controller)奠定了基础。
缺点
复杂性。MVC 模式引入了新的间接级别,因此稍微增加了解决方案的复杂性。它还增加了用户界面代码的事件驱动特性,调试用户界面代码会变得更加困难。
频繁更新的成本。将模型与视图分离并不意味着模型的开发人员可以忽略视图的特性。例如,如果模型发生频繁更改,则它可能向视图发出大量更新请求。一些视图(如图形显示)的显示可能需要一定时间。因此,视图可能滞后于更新请求。因此,在对模型进行编码时牢记视图是很重要的。例如,模型可以将多个更新作为单个通知发送到视图。
本文整理自《使用 Microsoft .NET 的企业解决方案模式》一书中的相关内容。
本文转自一个程序员的自省博客园博客,原文链接:http://www.cnblogs.com/anderslly/archive/2007/07/27/mvcpatterninaspnet.html,如需转载请自行联系原作者。