后知后觉:企业应用架构模式(二)
 

2010-01-06 作者:twdwyp 来源:博客园

 

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);另一种比较常见的是集合的方式,如数组或者字典格式,推荐使用字典格式, 因为可以使用有意义的字符串,不过,使用这种方式我们将失去清晰地接口和强类型所带来的好处。

  • 序列化

数据对象的一个必然的要求就是能够序列化。Java中内置了序列化为二进制的方法,而.net也提供了二进制及XML的序列化方法。也可以自己写对象序列化和反序列化的方法。

  • 领域对象与数据传输对象结合

一个数据传输对象并不知道如何与领域对象相关联,因为它被部署到连接的两端,因此,数据传输对象不应该依赖于领域对象,当然领域对象也不应该依赖于数据传输对象,因为当接口改变是,数据传输对象的地结构也会相应的变化,作为一个通用的规则,领域对象应该独立于接口。

对于这种情况,可以制作一个独立的组装器对象,它负责从领域对象组装成一个数据传输对象或者从一个数据传输对象更新领域对象。组装器是影射器的一个例子,它在数据传输对象和领域对象间建立影射关系。比如,我们常常试图把集合或者Dataset中的数据写一个通用的方法,生成SQL,从而更新到数据库相应的表中。

11 离线并发模式

一个业务的执行可能要跨越一系列的系统事务,一旦超过单个系统事务的范围,就不能仅依靠数据库管理程序来确保事务,比如当两个会话同时处理一个记录的时候,就很难保证数据的完整性。通常可以使用锁机制来解决这个问题。

11.1 乐观离线锁

验证一个会话提交的修改不会与其他会话中的修改发生冲突。

乐观离线锁的实现方法通常可以通过在记录后面增加版本号来解决,也可以在版本号后面使用最后修改时间和修改人作为补充。也有使用时间戳来替代版本号的,但是这种方法在遇到应用跨多台服务器时系统时钟将非常不可靠。

使用乐观离线锁不能解决不一致读问题,比如一个线程正在修改,而另一个线程使用未修改的数据进行计算。而考虑到企业中的并发问题,大多数是一个领域问题,而不是一个技术问题。

日常中常见的乐观离线锁的系统是源代码管理系统。

11.2 悲观离线锁

假设会话冲突的可能性很大,只有获得了锁,才能对记录进行操作,可以控制对读读不互斥,对读写,写写进行互斥操作。但是这样会对系统并发性进行限制。

11.3 隐含锁

通常锁的申请与释放都是开发人员显式进行的,通常比较容易遗忘,对于必须需要加锁的地方,我们可以在模板方法中强制申请或者释放锁。

12 会话状态

大部分的应用都是需要会话状态的,无状态的应用也是少量存在的如你输入一个身份证号,查询出成绩一样,但是当你要进行后续操作的时候,就又需要会话状态了,比如你需要查询出的成绩后进入填写其它信息的时候。会话状态根据存贮的位置可以分为客户端状态、服务器状态、数据库状态。有些应用会综合这几种存放方式,比如cookie是客户端状态,而asp.net中的session,则综合了客户端状态(cookie)和服务器状态。

1 3 基本模式

13.1 入口

入口是一个很简单的包装器方法,封装外部的资源,提供一组API或者服务接口。如果必须通过一个复杂的接口与外部操作,则应该考虑入口模式,这样使用入口将复杂性封装起来,不至于蔓延到整个系统中去,并且,替换资源非常简单。

入口模式可以参考外观(Fa?ade)模式,适配器模式(adapter)。

13.2 映射器

映射器通常在子系统或者层与层间交换数据,主要作用是把子系统或者层间进行解耦和,常见的映射器是数据映射器。映射器与入口的区别在于,只有任何一方都没有相互依赖的时候使用映射器模式,在隔离不同组件的应用方面来说,类似于桥接模式(bridger)或者中介者模式(mediator)

13.3 层超类型

在某一应用中,如果其中所有的对象都具有某些方法,且不希望这些方法在系统内被多次复制,则可以将这些方法移到一个通用类中,即层超类,比如domain object类可以是领域中所有对象的超类,而在.net framework中,所有对象都继承于object对象,object对象有五个共用方法Equals(),GetHashCode(),GetType(),ReferenceEquals(),ToString()。还有受保护方法MemberwiseClone(),Finalize().

13.4 分离接口

在开发系统中,为了减少系统部件之间的耦合程度来改进设计质量,我们会把类进行分组,然后组织成包或者程序集的概念。并限制包之间、程序集之间的依赖关系,比如领域层的类不能调用表现层的层的类一样。但是,有时候需要调用在一般性依赖有冲突的方法,在这里,可以通过将接口分离到一个单独的包或者程序集中,这样,调用方将不必知道实现的存在。

13.5 注册表

对于某些公用的对象,我们需要在程序的各个地方使用它,我们就可以将这些对象管理起来,并提供通用的访问接口来访问这些对象。这样可以减少代码量,通常注册表实现起来会有一个静态的访问接口,还有一个对象的管理类。如果不用注册表模式,我们可能需要把共有对象作为参数在构造方法间传递。在日常应用中,cache通常类似于注册表的概念。

13.6 值对象

我们对于日期或者货币类型我们可以声明为值对象来处理它。值对象在非纯粹的面向对象语言中,类似于基本类型。引用对象与值对象的基本区别在于判断两个对象是否相等的方法,引用判断是基于标示,而值对象是根据里面的属性值。

值对象并不作为完整记录来持久化,通常最为嵌入值或者序列化。在C#中,可以通过集成System.ValueType来自定义值对象。

13.7 货币类

在多数语言中并没有把货币作为基础类型,我们可以提供一个货币类,用来负责货币的计算、兑换、舍入取整。

货币类中最麻烦的方法是舍入取整问题,比如5分钱,分为30%,70%两份,则分别为1.5,3.5,两个数分别取整为6或者4,则平白多或者少了一分,这种通常可以使用划分器来实现。比如调用money.allocate(3,7)返回一个划分好的数组。

13.8 特殊实例

空值对于面向对象的系统比较难于处理,因为它会破坏多态,当我们返回一个对象可能为空,就需要在使用它之前进行空值测试,如果在程序中写入大量的判空代码,则会造成代码冗余。解决方法是,定义一个特殊的类,比如空对象类。或者有业务意义处理特殊情况的类,比如,一个空的顾客对象可以认为是一个还没有ID的顾客,他有一些特殊的处理方法。

13.9 插件

当应用程序的代码需要在多个环境中运行,并且每个环境的特定的行为需要不同的实现时,通常会采用分离接口的方法,同时采用工厂方法来生成所需要的对象,但是这时候如果多个应用可能需要多个工厂方法,会造成冗余和混乱,这时候如果只提供一个使用根据配置能够生成符合要求的插件工厂方法,解决了这个问题。

插件模式在具有反射机制的程序语言中能充分发挥其优势,可以在配置文件中包含从接口名到实现类名的映射。

13.10服务桩

当我们需要依赖于某个第三方服务的时候,如果依赖于不受自己控制的地外部资源,通常会增加软件项目的风险,在测试的时候,如果依赖于自己的在内存中的服务桩来替代服务会改善开发的过程。

通常,我们会使用一个入口定义一个服务的访问点,从而将资源的访问封装成为一个接口,然后再开发一个调用实际服务的实现和一个服务桩的实现,通过配置文件控制目前采用哪种实现方式,从而避免了对外部环境的依赖。

13.11数据集(dataset)

数据库中表示数据的主流方式是关系表格形式,在此基础上出现了很多的快速用户界面开发工具,这个用户开发工具提供的界面控件可以简单的绑定数据源就可以实现对数据的增、删、查功能了。但是这些工具的一个缺点就是没有预留存放业务逻辑的部件,比如日期是否合法等稍复杂点的检验,业务规则,和业务计算的地方,通常我们开放中会把这些代码放到存储过程中,或者在界面代码混杂到一起。

记录集模式的思想就是通过一个内存中的基于查询结果的结构,并提供一个对这个记录集进行操作的通用方法。这样,我们就可以利用基于关系型数据的用户界面开发工具的便利,又可以方便的使用领域逻辑代码访问数据。

记录集通常已经由平台提供商创建好了,如ado.net中的dataset和JDBC中的rowset。在此基础上,针对记录集的两个应用仍是十分有价值的,一种是能够断开数据源连接的能力,这样可以方便的传递数据,但是要解决数据的离线并发更新问题,另外一种就是访问数据的方法,通常可以使用 [数据集名称].[表名][行名][字段名称]的方法访问某项数据,但是如果为了能够更简单的访问数据,我们可以提供一个强类型的数据集合,在强类型的数据集合下,我们访问数据的方式通常可以通过 对象名(表名).属性名来得到,这样将根符合面向对象的编码规范。

火龙果软件/UML软件工程组织致力于提高您的软件工程实践能力,我们不断地吸取业界的宝贵经验,向您提供经过数百家企业验证的有效的工程技术实践经验,同时关注最新的理论进展,帮助您“领跑您所在行业的软件世界”。

资源网站: UML软件工程组织