1.模型-视图-控制器
把用户界面交互拆分到不同的三种角色中。
1.运行机制
MVC包括3个角色。模型:一个表示领域信息的对象,它是一个不可见对象,包括除了用于UI的那部分数据和行为之外的所有的数据和行为。在纯面向对象的概念里,模型是
领域模型中的一个对象。视图表示UI中模型的显示。因此,加入我们的模型是一个客户对象,那么我们的视图可能充满着UI窗口小部件或者是一个HTML页面,这些界面呈现模型的信息。
视图仅仅显示信息,任何信息的变化都由MVC中的第三个成员来处理,这个成员就是控制器。控制器获取用户输入,对模型进行操作并使视图得到适当的更新,从这个角度来说,UI是视图和
控制器的结合体。
考虑mvc的时候,关注2个主要的分离:从模型中分离表现和从视图中分离控制器。
从模型中分离表现是一个好的软件设计方案中最基本的启发方法。这个分离很重要,原因如下:
1.从根本上说,表现和模型的关注点不同。当开发视图时,你考虑的是UI机制以及怎样布局一个好的用户界面。当你设计模型时,你考虑的是业务策略,或数据库交互。
2.根据上下文,对于同样的基本模型信息,人们喜欢从不同,人们喜欢从不同的角度看待。表现和视图的分离允许你开发多个表现 --- 事实上,它们是完全不同的界面---但是使用
相同的模型代码。最显著的是,可以在相同的模型上实现富客户端,web浏览器,远程api和命令行界面。甚至在一个web界面上,你可以在同一应用程序的几个不同点上有不同的用户
界面。
3.不可见对象通常比可见对象容易测试。把表现和模型分离,允许你轻松的测试所有的领域逻辑,而不必求助于类似笨拙的GUI脚本工具这样的东西。
这个分离的关键点是依赖的方向:表现依赖模型,但是模型不依赖表现。人们在模型中编程的时候,必须完全不知道是哪个表现在起作用,这样可以简化他们的任务,并且将来容易增加新的
表现。同时,这也意味着表现也可以自由变化而无需改变模型。
这个原理引入了一个常见的问题。在多窗口的富客户端界面中,很可能将一个模型的多个视图同时显示在一个屏幕上。假如一个用户通过一个视图改变了模型,那么其他的视图也需要改变。
要在不建立依赖的情况下达到这个目的,需要一个观察者模式的实现,例如事件传播或者监听者。视图充当模型的观察者:一旦发生变化,模型就会发送一个事件,表现层随之进行更新。
第二个分离,视图和控制器的分离,就不那么重要了。
2.使用时机
mvc的价值主要是两个分离。其中视图和模型的分离是最重要的软件设计准则之一。只有系统非常简单并且没有任何实际行为的时候,你才无需遵守这条准则。一旦你有了一些不可见的
逻辑时就必须注意实现分离。
2.页面控制器(Page Controller)
在web站点上为特定页面或者动作处理的请求对象。
当你打开一个静态网页的时候,你需要向web服务器提交html网页的名称或者其所在的路径。网站在服务器上为每一个页面都保留一份单独的文档。动态网页则来的更有趣一些,因为在路径
名称和回应文件之间都有着很复杂的关系。但是,用路径通向文件的方法来处理请求是一种很容易被理解的简单模型。
总的来说,页面控制器再web站点上为每一个逻辑页面都准备了一个输入控制器。这个输入控制器可能是页面本身,因为它通常在服务器网页环境中,它也可能是一个对应的页面单独对象。
1.运行机制
页面控制器的基本思想是:为web站点上的所有页面都在web服务器上准备一个模块,这个模块充当控制器的角色。实际上,不是每个页面正好一个模块,因为,当你点击一个页面的
时候,根据动态信息的不同,你可能得到不同的页面。更严格的说,控制器绑定在每一个动作上,这个动作可能是点击一个链接或者一个按钮。
页面控制器可以被构造成一个脚本(CGI脚本)或一个服务端页面(ASP,PHP等)。在使用服务器页面时,通常要结合页面控制器和模板视图,并把他们放在同一个文件中。比较而言,
模板视图比页面控制器更好,因为要正确搭建模块是一件很麻烦的事。当网页的显示比较简单的时候,这不成问题。可是,从请求中提取出的数据如果有逻辑关系,或者决定哪个实际视图
将被显示时也涉及逻辑关系的话,那么你就不得不最终在服务器页面中使用麻烦的scriptlet代码了。
解决scriptlet代码复杂性的一个办法是使用辅助对象。如果这样做,服务器页面要做做的第一件事情就是调用辅助对象来处理所有逻辑。处理完之后,辅助对象将控制器交给原来的
服务器页面,或者将它重新定位到不同的服务器页面---充当一个视图的角色。这样,服务器页面就成为请求的处理程序,但是,大部分的控制器逻辑还是在辅助对象中。
另外一种方法是让脚本成为处理程序和控制器。在这种方法中,web服务器将控制器交给脚本;脚本本身就肩负着控制器的责任,而且最后,它还要负责定向到一个合适的视图以显示结果。
页面控制器的责任有:
1.url解码并获取所有必要的数据,以便为下一步的动作计算出所有需要的信息
2.创建和调用模型对象来处理数据。所有从html请求中得到的相关数据都应该被送到这个模型中,这使得模型对象不需要和html请求有任何连接。
3.决定哪个视图将用来显示结果页面,并且把模型的相关信息传递给它。
页面控制器不一定是个类,它可以调用多个辅助对象。如果有许多相似的任务要完成,这将非常有用。一个辅助类可以用来放置代码,使得这些代码不能被到处复制。
我们可以使用服务器页面来处理一些url,而使用脚本来处理另外一些url。如果一些url没有或者只有很少的控制器逻辑,则服务器页面是处理它们的最佳选择,因为服务器页面可以提供
易于理解和修改的简单机制。一些具有负责逻辑的url应用脚本来处理。
2.使用时机
主要的决策点是使用页面控制器还是使用前端控制器?在这两者中,页面控制器是我们非常熟悉的,并且它将引导我们使用一个很自然的结构化机制,在这中间,特定的动作将被特定的
服务器页面或者脚本类所处理。而对于前端控制器而言,你必须在它的较大的复杂性和它所带来的各种好处之间进行抉择,而前端控制器的大部分好处是具有更大导航复杂性的web站点中
是很重要的。
页面控制器再站点只拥有简单控制器逻辑的时候工作的很好。在这种情况下,大部分的url将被服务器页面处理,只有少数复杂的url由辅助对象处理。当你的控制器逻辑很简单时,前端
控制器将增加许多开销。
class ArtistController
{
public void doGet(HttpServletRequest request,HttpServletResponse response)
{
throws IOException, ServletException {
Artist artist = Artist.findNamed(request.getParameter("name"));
if (artist == null)
forward("/MissingArtistError.jsp",request,response)
else {
request.setAttribute("helper",new ArtistHelper(artist));
forward("/artist.jsp",request,response);
}
}
}
}
首先控制器需要创建出一个必要的模型对象来做处理,在这个程序里,我们只需要找出一个正确的模型对象来显示。其次,控制器把正确的信息放到http请求中,这样jsp就可以正确的
显示。在这种情况下,控制器创建一个辅助对象并把这个对象放到请求中。最后,控制器通过定向到一个模板视图来处理显示。定向是常见的行为,所以它很自然的放在所有页面控制器的
超类中。
模板视图和页面控制器之间最主要的耦合点是参数的名称,参数名称存在于请求中,用来传递jsp所需的任何对象。
3.前端控制器(Front Controller)
为一个web站点处理所有请求的控制器。
在一个复杂的web站点中,处理一个请求时,你需要做很多相似的工作,包括:安全认证,国际化,为特定用户提供视图。如果输入控制器的行为分散在多个对象中,这种行为的相当一部分
最终会被各处复制。同样,在运行时改变行为也很难。
前端控制器通过引导请求经过一个处理程序对象来统一所有的请求处理。这个对象可以执行一些通用的行为,并且可以在程序运行时使用decorators来修改这些行为。然后,处理程序就
调度一些command对来来处理某一请求的特定行为。
1.运行机制
一个前端控制器处理一个web站点的所有调用,它通常有2部分组成:一个web处理程序和一个command层次结构。web处理程序是一个实际上接收来自web服务器的post或get请求的对象。
它从url中得到足够的信息,并且决定下一步的动作是什么,然后,委托command执行动作。
web处理程序几乎都是作为一个类而不是一个服务器页面来实现的,因为它们不产生任何回应消息。command同样也是如此,实际上,它不需要一点web环境的只是,虽然它经常传递http
消息。web处理程序本身常常是一个简单的程序,这个程序除了决定运行哪个command不做任何事情。
web处理程序可以动态或者静态的决定运行哪一个command。其中的静态行为包括解析url并使用条件逻辑;动态行为包括提前url中的某个标准部门和用动态实例化去创建一个command类。
静态方法的好处有以下3点:简明清晰的逻辑;在调度时可以执行编译时错误检查;在分析url的时候可以拥有更多的灵活性。动态方法的好处是允许你在不改动web处理程序的情况下增加新
的command。
使用动态调用,你可以把command类的名字放到url中,或者你可以使用属性文件把这个名字绑定到url上。属性文件是另外一个要编辑的文件,但是,它通常会使你很容易的在不对web
页面进行大量搜索的情况下就改变类的名字。
对于前端控制器来说,一个非常有用的模式是截取筛选器。基本上,这是一个装饰,用来对前端控制器的处理程序进行包装。它允许你建立一条筛选器去处理诸如认证,登录,现场识别的
问题。使用筛选器将允许你在配置时动态的创建要使用的筛选器。
Rob Mee给展示了一个不大一样的前端控制器,它使用了两阶段的web处理程序,分为一个退化的web处理程序和一个调度的程序。退化的web处理程序从http参数中提取基本的数据,并
把这些数据送入到调度程序中,这样就可以实现调度程序和web服务器框架的完全分离。
记住处理程序和command都是控制器的一部分。因此,command能够为应答选择一个可用的视图,处理程序的责任就是选一个适当的command取执行相关的任务。
4.模板视图
通过在HTML页面中嵌入标记向HTML发送信息。
静态html页面对于不同的请求都不需要改变自身。我们这里讨论动态网页 --- 这些网页也许会对数据库进行查询,并把结果放入html网页中。这些网页会根据不同的查询结果呈现不同的
样式,这就会导致常规的html编辑器不再适合进行网页设计。
对于这个问题,最好的工作方式就是像创建静态页面一样创建动态页面,但是在这些页面中插入标记,这些标记可以被解释成收集动态信息的调用。因为处理特定响应时,网页中的静态部分
充当着模板的角色,所以称之为模板视图。
1.运行机制
模板视图的基本思想就是在静态网页中插入标记。在用网页处理一些请求的时候,标记会被一些计算结果所代替。用这样的方法网页可以被布局成通用的样式。
1.插入标记
模板视图最流行的一种方式是服务器页面,例如ASP,JSP或PHP。允许你把任意的程序设计逻辑(称之为scriptlet)放到网页中。然而,这种特性是一个大问题。把大量的scriptlet
放到网页中的最明显缺点是它削弱了非编程人员修改网页的可能性,尤其是使用了图像设计器时这点更为严重。然后,在网页中嵌入scriptlet的最大问题是页面并非程序中的良好模块。
即使使用面向对象的编程语言,页面构造也丧失了许多结构特性,而这些结构特性正是能使你有可能面向对象或者过程风格进行模块设计的那些特性。
更糟糕的是,在网页中放入大量的scriptlet很容易混合企业应用的不同层次。当领域逻辑在服务器页面出现的时候,组织它们还会变得异常困难,并且它们在不同的服务器页面间
很容易被复制。
2.辅助对象
避免使用scriptlet的关键在于在每个网页中提供一个常规的对象作为辅助对象,这个辅助对象拥有所有真实的程序设计逻辑。网页中你只需要调用它,这样简化了网页---这才是真正的
模板视图。用这种方法就可以允许非编程人员去修改网页,并使程序员的精力集中在辅助对象上。
3.条件显示
一个非常棘手的问题是带有条件的网页行为。带来的麻烦是:不得不将这些模板本身转换成某种程序设计语言。这将导致你再次面对把scriptlet嵌入到网页中所遇到的所有同样的问题。
如果你正在有条件的显示某些文本,一个选择是把条件放到辅助对象中,那么野蛮也将总是把请求返回的结果放入到请求对象中。如果条件不成立,辅助对象将会返回一个空的字符串,但是
这种方式中辅助对象将掌握所有的逻辑。
4.迭代
对一个集合的迭代表达了相似的问题。
5.什么时候运行它
从模板视图的名字中你可以很容易的知道,这种模式的主要功能是在mvc中扮演视图的角色。对于许多系统,模板视图应该只承担视图的角色。但在一些简单的系统中,它扮演
控制器角色甚至是模型的角色。当模板视图所承担的责任超过了视图的范围时,那么尤为重要的是一定要保证让辅助对象而不是页面来处理这些责任。控制器和模型的责任包括程序
逻辑,它像随处可见的程序逻辑一样都应该放在辅助对象中。
任何模板系统都需要由服务器页面进行额外的处理。这一点可以通过在建立网页后对其进行编译,也可以在接收第一次请求时编译网页,还有一种可行的方法就是通过在每次请求
时解释网页的方法来实现。
在使用模板视图的时候,另外一个需要注意的地方是异常。如果让异常处理按照它自己的方式工作,你可能会发现网页并不完全受自己控制,因为网页可以由于某种异常而导致
一些出乎意料的输出。
6.使用脚本
如今,服务器页面已经成为模板视图的一种最常见的形式,但是你还是可以在模板视图中编写脚本。
2.使用时机
为实现mvc中的视图,你需要在使用模板视图还是使用转换视图中做出选择。使用模板视图的好处是通过观察网页的结构可以组成网页的内容。对于大多数人既易学
又易用。特别是模板视图能很好的支持图形设计者设计网页,程序员在辅助对象上编程这种方式。
模板视图也有2个弱点。第一,普通的实现使得网页很容易被插入复杂的逻辑,这就导致了网页很难维护,尤其是当维护人员是一个非编程人员时。你需要一个号的规则使网页
保持简洁,并且便于显示,同时把逻辑放入辅助对象中。第二个弱点是模板视图要比转换视图难测试,大部分模板视图的实现设计成在服务器页面中工作,这将导致模板视图
难以甚至不可能调试。相比之下,转换视图的实现很容易测试并且不需要运行服务器页面。
5.转换视图
一个视图,它一项一项的处理领域数据,并且把它们转换成html。
当你向领域层或者数据源层请求数据的时候,你将会得到所有需要的数据。但是,这些数据没有格式化,并且你还需要一个合适的web页面来显示这些数据。在mvc中视图的任务
就是向web网页发送这些数据。那么使用转换视图意味着把这种视图当做一种转换,它把模型中的数据作为输入并且把html作为输出。
1.运行机制
转换视图的核心思想是写一个查看面向领域的数据并将其转换成html内容的程序。这个程序将访问领域数据的结构,它能识别领域数据中的每种形式,为它们写出特殊的html。
转换视图和模板视图之间最重要的不同点是组织视图的方式不同。模板视图的组织是围绕输出的,而转换视图的组织是围绕为每种输入元素所准备的单独转换而形成的。这些转换
由某些类似简单循环的东西所控制,它们查看每一个输入元素,并为输入的元素寻找合适的转换,最后实施转换。典型的转换视图的规则可以查看任何顺序排序,而且不影响输出
结果。
2.使用时机
选择转换视图还是模板视图主要取决于从事视图软件工作的团队偏爱哪一种环境。在这里,工具的选择很重要。
转换视图避免了模板视图的两个大问题。转换视图可以更容易的把转换焦点放在绘制的html上,因此可以避免在视图中引入太多其他逻辑。它还可以很容易的运行转换视图并
捕获测试的输出。这使得测试视图变得很容易且你不需要一个web服务器来运行测试。
转换视图可以直接把面向领域的xml转换成html。
6.两步视图
用2个步骤把领域数据转换成html:第一步,形成某种逻辑页面;第二步,把这些逻辑页面转换成html页面。
如果你的web应用程序包括许多页面,那么你总是希望这些页面都有一致的风格和组成形式。如果站点中每个页面看起来都不尽相同,用户浏览页面就会感觉混乱。你或许希望可以轻松
的对网站中所有的页面进行全局性的外观改变,但是由于表现层的决定经常在许多页面或转换模块中重复,因此当你使用诸如模板视图和转换视图这样的通用方法时,它们会把事情搞得
更糟糕。一个全局性的变化将被迫使你去改变好几个文件。
两步视图通过把转换分解成2个阶段来解决这个问题。首先,它把模型中的数据转换成不带任何详细格式信息的逻辑展示出来;其次,再把这个逻辑表示转换成需要的实际格式。通过
这样的方法,当要进行全局性的变化时,你只需要改变上述两个阶段的第二个节点。同样,通过上述的方法,你或许可以支持多重输出的外观和感觉,每种用一个第二阶段。
1.运行机制
这种模式的关键之处在于要把向html的转换过程分成两个阶段来处理。第一阶段,把信息装配到一个逻辑屏幕结构中,该结构可以作为显示元素的参数,但不包含html。第二阶段,
获得面向表现的结构,并把它放入html中。
这种中间形式是一种逻辑屏幕。它的元素可以是字段,标题,页脚,表格,选项和其他一些类似的东西。因此,它肯定是面向表现的并且一定会使得屏幕的实现遵循一个明确的样式。
你可以把面向表现的模型想象成这样一个东西:它可以定义你可能拥有的不同的小窗口部件,它还可以定义这些小窗口部件所包含的数据,但是这些小窗口和数据却都不想洗说明html
的外观。
这种面向表现的结构由针对每个屏幕的特殊代码组装而成。两阶段中的第一个阶段的职责有以下三个:第一,去访问一个面向领域的模型,这个模型或许是一个数据库---真实的
领域模型,或者是一个面向领域的数据传输对象;第二,为屏幕提取相关的信息;第三,随后把这些信息放入面向表现的结构中。
第二阶段的职责是把面向表现的结构转换成html。它知道面向结构中的每个元素以及怎样把这些元素转换成html。因此,一个由许多屏幕的系统将被一个单一的第二阶段转换成
html,确保了所有的html显示格式的抉择都将在同一个地方完成。
有许多方法可以建立两步视图,其中一种最简单的方法是使用两步XSLT。单步XSLT是转换视图中的方法。另一种方法是使用类。这里你可以把面向表现的结构定义成一些列的类:
表类,行类等。这样,第一阶段的职责是获得领域信息和并将这些类实例化进入一个用模拟逻辑屏幕的结构。第二阶段的职责是把这些类转换成html,第二阶段的工作是可以通过让
每一个面向表现的类来自己产生html,也可以通过一个独立的html renderer类来完成。
2.使用时机
两步视图的主要价值来源于它分离了第一和第二阶段,使你可以很容易的进行全局性的修改。这让我们联想到两种情况:多外观web应用和单一外观web应用。多外观应用现在使用的
很少,但是这种应用正在逐步增长。这其中,多重结构提供相同的基础功能,每一个结构都有它自己的样式。单一外观的应用更为普遍。只需要面向一种组织结构,而且他们需要整个
站点有一只的外观。所以这种优先考虑。
使用一个单阶视图(可以是模板视图,也可以是转换视图)时,需要为每一个web网页建一个视图模块。使用两步视图,你可以有2个阶段:针对每个页面,有一个第一阶段模块;针对
整个应用,有一个第二阶段块。使用两步视图的好处是很容易改变你网站的样式。
两步视图的另外一个缺点是需要使用工具。现有的许多工具让没有变成能力的设计者使用模板视图设计网页,但是两步视图要求程序设计人员去写绘制器和控制器对象。
7.应用控制器
一个用来处理屏幕导航和应用程序流的集中控制点。
许多应用程序都包含大量重要的屏幕逻辑,这些逻辑将在不同的地方应用。在一个应用程序中,可能在某一确定的时间点上调用一个确定的屏幕。这是导航风格的交互,在此,用户将会
按照一系列确定的顺序来访问屏幕。另外一种可能是只有在某种确定的条件下我们才能看到某一屏幕,或者根据我们先前输入的来选择不同的屏幕。
在某种程度上,各种mvc的输入控制器可以做出一些觉得,但是随着应用程序复杂性提高,当不同屏幕的若干控制器都需要知道在某一特定环境下需要做什么时,这种方法会导致代码的
重复。
你可以将所有的流逻辑放入到应用控制器中来避免大量的重复代码。之后,输入控制器向应用控制器请求正确的命令来执行,这个命令是相对于模型的;同样,输入控制器还要从应用
控制器中得到正确的视图,这个视图要根据应用的上下文而定。
1.运行机制
应用控制器有两个主要的职责:决定运行哪个领域逻辑和决定用哪种视图来显示应答消息。为了完成上述的功能,一个应用控制器通常需要维持两个指向的引用集合:一个指向
领域命令,这个对象可以在领域层指向;另外一个指向视图。
针对领域命令和视图,应用控制器需要找到一种方法来存储它所调用的东西。命令模式是一个好的选择,因为它可以让你很容易的获取并运行一段代码。具有函数概念的编程语言
可以使用函数指针。另外一种方法是维护一个字符串,这个字符串可以被用于反射机制来调用某些方法。
领域命令可以是一个命令对象,这个对象将作为应用控制器层的一部分。领域命令还可以是指向一种事务脚本的引用,或者是领域层中的领域对象方法。
如果你正在使用服务器页面作为你的视图,那么你可以使用服务器页面的名字。如果你正在使用一个类,那么为反射调用一个命令或者字符串是有意义的。同样也可以使用一个
xslt转换,这样应用控制器将维持一个指向这个转换器的字符串。
基于种种原因,人们更愿意把应用控制器作为表现层和领域层之间的一个中间层。
一个应用程序可以拥有多个应用控制器,每个应用控制器都要处理应用程序的一部分。这样就允许你把复杂的逻辑分开,并放入到各个类中。在这种情况下,通常把工作分隔开,
并把它们放入到用户界面的各个区域中。然后,为每一个区域都创建一个单独的应用控制器。一个简单的应用程序只需要一个应用控制器。
如果你有多种表现,如:web前端,富客户端和PDA,你或许能对每个表现层使用相同的应用控制器,但是,不要觉得这样很好。通常来说,不同的UI需要不同的屏幕数据流来
实现真正的可用的用户界面。然后,重用一个应用控制器可能会减少开发工作量,但这些减少的工作量可以以糟糕的UI作为代价。
通常把UI当做一个状态机,在这里,根据程序中的某个关键对象的状态来决定某个事件将触发哪个应答消息。在这种情况下,应用控制器有责任使用元数据来表现状态机控制流。
元数据可以通过持续语言调用来建立,还可以被存储在一个单独的配置文件里面。
2.使用时机
如果你的应用程序的流程和导航足够简单,以至于任何人都可以以任何顺序来访问你的任何屏幕,则应用控制器对你而言是没有任何价值的。应用控制器真正的优势在于它网页
访问顺序的明确规定和根据对象的状态来显示不同视图的性质。