后知后觉:企业应用架构模式(二)
8 对象元数据映射模式
大部分代码都用来处理对象—关系的映射,即处理数据增、删、改、查的操作。这些代码冗长而且重复。而这些元数据映射使开发者可以使用一个标准格式来描述映射,并用通用的代码去处理映射。
通常使用两种方法来实现元数据映射,代码生成及反射。
代码生成即根据元数据描述,生成相应的增、删、改操作。如hibernate,codemetric代码生成工具。
反射通常提供一个传入对象名字(标识)的方法,通过传入的对象的名字从元数据中加载此对象的元数据映射。
元数据映射方式,在大多数简单的映射处理的很好,但是往往会因为处理复杂的映射增加元数据的复杂性,遇到这种情况,最好的方式是使用特殊的类来单独处理这种情况。元数据方式也会造成代码重构的困难。但是,元数据另一方面可以看作是数据库与程序之间的接口,因此数据库的重构可以通过元数据的改变而改变,从而避免了程序的改变。
查询对象:
提供一个隐藏了SQL内部参数的查找器来实现对SQL语句的封装。查找对象本身是一个解释器,是解释器模式[GOF设计模式]在SQL查询上的应用。通过对象之间的引用来替代表和列的关联来创建查询。
采用这种方式编写查询的人可以设计出独立于数据库方案的查询,并且数据库的变化也可以封装到局部。同时,基于这种查询可以一种高级的应用,比如,可以消除对数据库的重复查询,当执行过一个查询之后,再次运行这个查询,系统会直接返回对象,避免重复查询,如果这个后一个查询是前一种查询的特殊情况(比如多了一个and条件)也会出现同样的问题。
为实现支持不同的数据源版本保持程序统一性的项目可能会使用查询对象,有一些通用的查询对象的产品,一般情况下,把SQL语句隐藏到封装好的查询器(查找方法)中是一个物美价廉的选择。
资源库(Repository):
复杂领域模型的系统中,由数据映射器提供的层分离了领域对象及数据库访问代码的细节。如果此类系统中存在大量的领域类或者繁重的查询时,在数据映射层之上再建立另外的一个抽象层资源层就更为重要了。
事实上,资源库看上去像是面向对象数据库的一小块,类似于查询对象。资源库对外提供一些简单的接口,客户创建一个条件对象来指定他们需要由查询返回的对象的特征,要根据姓名来查找对象,首先创建一个条件对象,把条件设置为 criteria.equals(person.name,”flower”),或者criteria.like(person.name,”M”),然后调用Repository.match(criteria),返回一个领域对象列表。使用资源库的方法,可以更好的隐藏后台数据源,方便数据源的替代。
9 Web表现层模式
9.1 MVC
MVC中应该注意两个分离:
视图和模型的分离,应该把模式从视图中分离出去,让视图依赖模型,这样视图的增加不会引发模式的改变。在[GOF设计模式]中介绍的MVC模式,有多个视图依赖于一个模型,如果一个视图通过添加数据等操作改变了模型,使用观察者模式,让视图作为模型的观察者来实现。当然,也可以在视图间增加显示的依赖,这样能解决问题,就是不完美了。
第二个分离:视图和控制器的分离:
视图和控制器分离的例子并不常见,在通常的CS程序中,通过提供两个控制器解决界面元素可编辑不可编辑状态是视图和控制器分离的一个用法。在BS结构程序中,控制器和视图存在天然的分离,因此MVC模式再次流行起来。
9.2 页面控制器
基本思想是为web上所有的页面在web服务器上准备一个模块,这个模块充当控制器的角色,并不是每个页面正好一个模块,而是一个连接或者按钮触发一个模块。
页面控制器可以是一个叫本(CGI、Serverlet)或者是一个服务器段页面(ASP,PHP,JSP)。
页面控制器的主要职责有:
1〉URL解码并获取相关数据,为下一步动作计算出需要的信息。
2〉创建和调用模型对象来处理数据,所有从html中得到的相关数据都被送到模型中。
3〉决定哪个视图显示结果,并把模型的相关信息传递给他。
9.3 前端控制器
在一个网站中,处理一个请求需要做安全认证、为特定用户提供特殊视图等操作,前端控制器通过 引导请求经过一个处理程序来统一所有的请求处理,其可以处理一些通用的行为。
* 前端控制器通常使用command对象,并通过一个筛选器来确定调用那个command对象处理请求。在.net中的httphandle就是这种方式处理的。
9.4 模板视图
模板视图的基本思想就是在静态页面中加入标记。在用网页处理一些请求的时候,标记会被一些计算的结果所代替。
插入标记的方式有一种是使用类似html的标记,比如<>,这样编辑器就可以忽略其中的内容或者对这些标记特殊处理,可以很好的适应所见即所得的编辑器。如果标签符合xml的书写规范,可以在结果文档上使用xml工具(页面必需是xhtml格式)。另一种标记是正文中使用特殊的文本标记,这些标记也不影响编辑器的识别而且比html,xml的标记简单。
模板视图中最流行的一种是服务器页面,如ASP,JSP,PHP,这种方式比基本的模板视图简单,而且允许将任意的程序逻辑放到页面中,但是这种方式并不值得提倡,首先消弱了非编程人员修改网页的可能性,其次页面构造破坏了程序的特性,这些特性其实应该是你的模块设计中应该考虑的特性,如封装、继承、耦合、内聚等特性,最重要的一点是,页面中的大量的scriptlet很容易混合企业应用的不同层次。
辅助对象,可以为每一个页面提高一个辅助对象,辅助对象提供程序逻辑,页面中只是调用辅助对象,这样的页面才可以称得上是标准的模板视图。只是这种方式应该避免使用辅助对象生成一段文本并放到网页上,比如 reponse.write(“<td>一些数据</td>”)这种方式。
在页面上可以使用一些条件和迭代,这样便于页面的内容的丰富展现,条件用来解决只有某个条件成立才显示的东西,迭代可以解决生成表格的问题。但是这样不得不把模板变成具有某些程序设计语言,但是这与scriptlet又不应该相同,因此,模板语言的应运而生了。比如velocity.
综上所述模板视图的关注点就是将得到的辅助对象放到请求中去,并在页面中能够接触到这个辅助对象。
9.5 转换视图
在MVC 模式中,视图的任务就是向Web页面发送数据,转换视图意味着把这种视图当作转换,它把模型中的数据当作输入,经过一步一步的转换,最终输出html化的数据,即把模型中的文本、表格最终以html格式表达出来。
转换视图的核心思想就是一段能将模型数据转换为html页面的一段程序。它与模板视图的不同在于,模板视图的组织是围绕输出的,而转换视图的组织是围绕为每个输入准备单独的转换。
可以使用任何语言编写转换视图,但是最流行的还是使用XSLT。如果学习过XML就应该对XSLT有所了解。
如果需要对网站进行改版,可能需要对每个转换器进行改造,也可以考虑使用XSLT将转换器作的尽量通用,这样改造量将比较小,当然,使用转换视图通常更容易做的比模板视图通用。不过解决网站通用改版的方式最好的还是 两步视图。
9.6 两步视图
如果你的应用程序中包含很多页面,而且希望能够轻松的地对网站中所有的页面进行全局性的外观改变,两步视图通过把转换分为两个阶段来解决这个问题,首先把模型中的数据转换成不带任何详细格式信息的逻辑表示,然后把这个逻辑表示转换成需要的html格式。
有许多方法可以建立两步视图,一种是方法是使用两步XSLT;另一种方法是使用类,把第一阶段的结果定义为一系列的类,如行类,表类等;上面这两种方法是基于转换视图的,也可以采用模板视图的方法,即在模板上写上一系列的标签,而不是html,这样不同的html风格可以通过使用不同的html替换标签实现,不过这种方式失去了所见即所得的html编辑器的能力。
多外观的应用是有实际意义的,当我们看到某些网站的时候,能够很明显的发现这些网站是使用同样的程序实现的,只是页面不同了。目前的站点中,如果每个页面都不同,即页面间的共性不多的话,使用两步视图的实际效果就不明显了。
*软件产品化的项目中可能更有实际意义的,我们公司的信息发布系统产品实际允许不同用户使用不同的界面的,不过我们的多外观是通过多套模板实现的,目前的大多数信息发布系统也是通过多套模板实现多个风格,可能有两种原因吧,一种是各种页面间的风格的共性不多,另一种是多套模板简单容易实现,不需要开发人员参与也可以实现,可见简单实用的技术在工业应用中还是比复杂的技术受欢迎的。
9.7 应用控制器
应用控制器用来控制导航及应用程序流的集中控制点,许多应用程序中存在大量的屏幕逻辑,即根据用户的应用时间、状态等来返回不同的页面,或显示不同的页面序列,这时候就需要应用控制器了。一个复杂的应用程序可以拥有多种应用控制器,每一个应用控制器负责处理程序的一部分,这样便于把负责的逻辑分开。
如果,你的程序中的某个流程地改变,你需要在程序中修改多处的时候,那么使用应用控制器的时机到了。不过,应用逻辑和领域逻辑的划分是一个头痛的问题,比如在人员管理类系统中,如果某个人的特殊情况(离异、特殊奖励等),而单独弹出页面进行补充信息的话,那么这个逻辑是放到应用逻辑中开还是放到领域逻辑中呢?经验标示,如果在这种情况发生在多个不同的地方,那么就建议添加领域逻辑中。
10 分布模式
10.1 远程外观
由于远程调用(包括进程间调用)的效率要远远低于进程内调用的效率,在远程调用访问中,常常为了避免多次调用,而将细粒度的对象封装起来,提供一个组粒度的调用接口,这就是远程外观。参考Façade模式[GOF设计模式]。
远程外观有有状态和无状态之分,无状态的远程外观可以组成池,提高资源的利用率,如果为有状态的就需要维护一个会话状态。
远程外观中可以提供访问控制和事务的功能,也可以提供异步访问的方法。但是,远程外观中切忌实现领域逻辑的东西。任何外观都应该是薄薄的一层,只负责提供访问接口的责任。远程外观的实现模式典型的有WebService,也可以使用EJB来实现有状态或无状态的分布应用。
10.2 数据传输对象
为了减少方法调用次数,而在进程间传输数据的对象,(传输对象也常用在层与层之间传递)。
在许多方面,数据传输对象都是我们被告知永远不要写的对象之一,它经常只不过都是一堆域及get和set方法,这种对象的价值就是,允许在一次调用中,传输不同的信息。通常,数据传输对象中存放的地数据量会大于远端对象所需要的数据量,我们宁可每次多传输一些数据以避免错误,也不愿意为了某些错误做多次的调用。
数据传输对象的常见格式是记录集(.net中为Dataset);另一种比较常见的是集合的方式,如数组或者字典格式,推荐使用字典格式, 因为可以使用有意义的字符串,不过,使用这种方式我们将失去清晰地接口和强类型所带来的好处。
l 序列化
数据对象的一个必然的要求就是能够序列化。Java中内置了序列化为二进制的方法,而.net也提供了二进制及XML的序列化方法。也可以自己写对象序列化和反序列化的方法。
l 领域对象与数据传输对象结合
一个数据传输对象并不知道如何与领域对象相关联,因为它被部署到连接的两端,因此,数据传输对象不应该依赖于领域对象,当然领域对象也不应该依赖于数据传输对象,因为当接口改变是,数据传输对象的地结构也会相应的变化,作为一个通用的规则,领域对象应该独立于接口。
对于这种情况,可以制作一个独立的组装器对象,它负责从领域对象组装成一个数据传输对象或者从一个数据传输对象更新领域对象。组装器是影射器的一个例子,它在数据传输对象和领域对象间建立影射关系。比如,我们常常试图把集合或者Dataset中的数据写一个通用的方法,生成SQL,从而更新到数据库相应的表中。