本页内容
上下文
问题
影响因素
解决方案
示例
结果上下文
测试考虑事项
相关模式
上下文
您已经决定使用Model-View-Controller
(MVC) 模式将动态 Web 应用程序的用户界面逻辑与业务逻辑分隔开来。您已经考察了Page
Controller模式,但您的页面控制器类具有复杂的逻辑,并且是较深的继承层次结构的一部分,或者,您的应用程序是基于可配置的规则来动态确定页面导航的。
问题
如何为非常复杂的 Web 应用程序构建最佳的控制器结构,以便在避免代码重复的同时实现重用性和灵活性?
影响因素
下面是适用于 Front Controller 模式的、由 Model-View-Controller
带来的各种具体的影响因素:
- 如果在系统的不同视图内复制公共逻辑,则需要集中此逻辑才能减少代码重复量。删除重复的代码是改进系统的总体可维护性的关键。
- 数据检索最好也集中在一个位置进行处理。一个好的示例是,让一系列视图使用数据库中的相同数据。与让每个视图检索数据并重复数据库访问代码相比,在一个位置实现对此数据的检索是更好的做法。
- 如 MVC 中所述,测试用户界面代码往往是耗时而乏味的。通过区分单各自的角色,可以提高总体可测试性。这不仅适用于模型代码(在
MVC 中已说明),而且适用于控制器代码。
以下影响因素可能使您决定使用 Front Controller,而不是 Page
Controller。
- Page Controller 的一般实现方法涉及为各个页面所共享的行为创建一个基类。但是,随着时间的推移,由于要增加非所有页面公用的代码,这些基类就会不断增大。若需要定期重构此基类以确保其只包括公共行为,则需要制定规则。例如,您不希望由页面检查请求并决定(基于请求参数)是否将控制权转移给另一个页面,因为这种类型的决定对于特定功能来说更具体,而不是所有页面共有的。
- 为了避免在基类中添加过多的条件逻辑,您会创建更深的继承层次结构以删除条件逻辑。例如,在具有三个功能区域的应用程序中,只使用一个包含应用程序公共功能的基类可能是很有用的。每个功能区域可能还有另一个类,该类继承总体应用程序的基类。乍一看,这种类型的结构是简单的,但它通常会导致非常脆弱的设计和实现,并给代码带来问题。
- Page Controller 解决方案描述了每个逻辑页面使用一个对象。当需要跨多个页面对处理过程进行控制或协调时,此解决方案将不可行。例如,假定在
Web 应用程序中具有复杂的可配置导航(以 XML 格式存储)。当收到请求时,应用程序必须根据其当前状态查找下一步要前进到哪个位置。
- 由于Page Controller 是通过每个逻辑页面使用一个对象来实现的,因此,很难在
Web 应用程序的所有页面中一致地应用特定操作。例如,安全性最好以协调方式实现。让每个视图或页面控制器对象分别处理安全性是有问题的,因为它可以被不一致地应用,并导致安全问题。此问题的其他解决方案还将在Intercepting
Filter 中进行讨论。
- 对于 Web 应用程序来说,URL 与特定控制器对象的关联可以是强制性的。例如,假定您的站点具有类似向导的界面用于收集信息。此向导包括许多必备页面和许多基于用户输入的可选页面。在使用
Page Controller 实现时,必须使用基类中的条件逻辑来实现可选页面,才能选择下一页面。
解决方案
Front Controller 通过让单个控制器负责传输所有请求,从而解决了在 Page
Controller 中存在的分散化问题。控制器本身通常分为以下两部分实现:处理程序和命令层次结构(见图
1)。
图 1:Front Controller 结构
处理程序具有以下两项职责:
- 检索参数。处理程序接收来自 Web 服务器的 HTTP Post 或 Get 请求,并从请求中检索相关参数。
- 选择命令。处理程序首先使用请求中的参数选择正确的命令,然后将控制权转移给该命令以便执行处理。
图 2 显示这两项职责。
图 2:Front Controller 的典型方案
命令本身也是控制器的一部分。命令代表具体的操作,这在 Command 模式 [Gamma95] 中有相应的介绍。通过将命令表示为单独的对象,控制器可以按一般方式与所有命令交互,这与调用公共命令类上的特定方法相反。在命令对象完成操作之后,将由命令选择使用哪个视图来显示页面。
示例
请参阅在
ASP.NET 中使用 HTTPHandler 实现 Front Controller。
结果上下文
Front Controller 模式具有下列优缺点:
优点
- 集中化控制。 Front Controller 用于协调向 Web
应用程序发出的所有请求。此解决方案描述了使用单一控制器,而不是 Page Controller
中所用的分布式模型。此单一控制器处于很好的位置来实施全应用程序范围的策略,如安全性和使用情况跟踪。
- 线程安全。由于每个请求都涉及创建新的命令对象,因此命令对象本身不需要是线程安全的。这意味着,命令类中避免了线程安全问题。但是,这并不意味着您可以完全避免线程问题,因为命令所作用的代码(即模型代码)仍然必须是线程安全的
[Fowler03]。
- 可配置性。只需要在 Web 服务器中配置一个前端控制器;处理程序执行其余的调度。这简化了
Web 服务器的配置。一些 Web 服务器是很难配置的。使用动态命令可以添加新的命令,而无需进行任何更改
[Fowler03]。
缺点
- 性能考虑事项。 Front Controller 是用来处理对 Web
应用程序的所有请求的单个控制器。在这两部分中,应该仔细检查处理程序中是否有性能问题,因为处理程序将确定负责执行请求的命令的类型。如果处理程序必须执行数据库查询或
XML 文档查询才能作出决定,则可能导致性能非常缓慢。
- 增加了复杂性。 Front Controller 比 Page
Controller 更复杂。它通常涉及将内置控制器替换为自定义的 Front Controller。实现此解决方案会增加维护成本和新手的学习难度。
测试考虑事项
从视图中删除业务逻辑简化了视图的测试难度,因为此后可以在独立于控制器的情况下测试视图。如 Page
Controller 模式中所述,测试控制器可能受到以下情况的阻碍:控制器包含了使其依赖于 HTTP
运行时环境的代码。通过使用两阶段的 Web 处理程序(如 Martin Fowler 所著的
Patterns for Enterprise Application Architecture
[Fowler03] 和 Page Controller 模式中所述),可以解决此依赖问题。控制器被分为两部分:Web
处理程序和调度程序。Web 处理程序从 Web 请求中检索数据,并以调度程序不依赖于 Web 服务器框架的方式(例如,在一个常规集合对象中),将其传递给调度程序。这样,就可以在没有
Web 服务器框架的情况下测试调度程序。
相关模式
有关详细信息,请参阅下列相关模式:
-
Intercepting Filter。此模式描述了在 Web 应用程序内实现重复功能的另一种方式。Intercepting
Filter 的工作原理是,先通过可配置的筛选器链传递每个请求,然后将控制权传递给控制器。筛选器往往会处理较低级别的功能(如解码、授权、验证和会话管理),而
Front Controller 和 Page Controller 则处理应用程序级别的功能。筛选器的另一个方面是它们通常是无状态的。例如,在开始对用户授权时,Web
服务器必须验证会话。如果用户已通过身份验证,则过程继续进行。如果没有,则将用户重定向到其他位置。Intercepting
Filter 的一个优点是,在大多数实现中,不必修改页面本身就能添加其他功能。
-
Page Controller。此模式是 Front Controller
的更简单替代方案。在 Page Controller 模式中,每个页面各有一个控制器对象,这与所有请求使用一个对象的方案相反。对于大多数应用程序来说,Page
Controller 是更合适的起点。仅当需要 Front Controller
时才应该使用它。
|