twdwyp

导航

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

 

1 1   离线并发模式

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

11.1    乐观离线锁

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

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

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

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

11.2    悲观离线锁

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

 

 

11.3    隐含锁

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

1 2      会话状态

大部分的应用都是需要会话状态的,无状态的应用也是少量存在的如你输入一个身份证号,查询出成绩一样,但是当你要进行后续操作的时候,就又需要会话状态了,比如你需要查询出的成绩后进入填写其它信息的时候。会话状态根据存贮的位置可以分为客户端状态、服务器状态、数据库状态。有些应用会综合这几种存放方式,比如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中的datasetJDBC中的rowset。在此基础上,针对记录集的两个应用仍是十分有价值的,一种是能够断开数据源连接的能力,这样可以方便的传递数据,但是要解决数据的离线并发更新问题,另外一种就是访问数据的方法,通常可以使用 [数据集名称].[表名][行名][字段名称]的方法访问某项数据,但是如果为了能够更简单的访问数据,我们可以提供一个强类型的数据集合,在强类型的数据集合下,我们访问数据的方式通常可以通过  对象名(表名).属性名来得到,这样将根符合面向对象的编码规范。

posted on 2009-04-17 01:37  twdwyp  阅读(2634)  评论(2编辑  收藏  举报