1、概述
2、分层
表现逻辑处理用户与软件间的交互。表现层的主要职责是向用户显示信息并把从用户那里获取的信息解释成领域层或数据源层上的各种动作。
数据源逻辑主要关注与其他系统的交互,这些系统将代表应用完成相关的任务。可以是事务监控器、其他应用、消息系统等。最主要的数据源逻辑是数据库,主要责任是存储持久数据。
领域逻辑也称为业务逻辑,是应用必须做的所有领域相关工作,包括根据输入数据或已有数据进行计算,对从表现层输入的数据进行验证,以及根据从表现层接收的命令来确定应该调度哪些数据源逻辑
3、组织领域逻辑
领域逻辑的组织可以分为三种主要的模式:事务脚本 、领域模型和表模块。
3.1 事务脚本
从表示层获得输入、进行检验和计算处理,将数据存储到数据库中以及调用其他系统的操作等。然后,该过程将更多的数据返回表示层,中间可能要进行大量的计算来组织和整理返回值。基本的组织方式是让每个过程对应用户可能做的一个动作,将这一模式想像成一个动作或业务事务的脚本。该脚本不必一定是单个的内嵌过程,可以分离成不同的子例程,这些子例程可以在不同的事务脚本之间共享,但是每一个动作还是由一个过程来驱动。
优点:
- 是一个大多数开发者都能理解的简单过程模型
- 能够与一个使用行数据入口或者表数据入口的简单数据源层很好的协作。
- 设定事务边界的方法显而易见,一个事务始于脚本的打开,终于其脚本的关闭。
缺点:
当领域逻辑的复杂性增加会凸现。当若干个事务需要做相似的动作时,通常使多个脚本中包含某些相同的代码。通过将这些代码提取出来组织成公共的子例程可以部分消除这种情况 ,但在许多时候,消除副本仍比较棘手,而检测副本则更为困难。
3.2 领域模型
不再是由一个过程来控制用户某一动作的逻辑,而是由每一个对象来承担一部分相关逻辑。
领域模型的价值在于可以运用许多现成的技术来较好的组织日趋复杂的领域逻辑。例如,当增加新的收入确认算法 时,只需要增加相应的新策略对象即可。而使用事务脚本则需要在脚本的判断逻辑中增加许多新的条件。
领域模型的开销来自使用上的复杂性和数据源层的复杂性。
3.3 表模块
与领域模型区别是对数据库中的每一个合同都有一个相应合同类的实例,而表模块只有一个公共的合同类实例。表模块设计成与记录集一起工作,因此,在一个用来处理合同的表模块中,客户需要首先对数据库进行查询以生成一个记录集,然后以记录集为参数创建一个合同对象。客户可以调用合同对象的方法来完成各种操作。
优点:表模块是事务脚本和领域模型的一个中间地带,是围绕表而非直接围绕过程来组织领域逻辑,提供了更多的结构,而且更容易发现和移除冗余代码。
缺点:无法应用许多在领域模型中可以使用的组织细粒度逻辑结构的技术,如继承、策略和其他面向对象的设计模式。
3.4 抉择
当领域逻辑很简单时,领域模型并不合适。
当领域模型和复杂度增加时,除领域模型之外的其他方法都不再适用,因为它们增加新功能的困难程序会随着系统复杂度的增加而呈指数增长。
是否就用表模块很在程度上取决于环境对通用记录集结构的支持,如果开发环境拥有大量基于记录集的工具,则表模块就很有吸引力。
3.5 服务层
处理领域逻辑的常见方法是将领域层再细分成两层。服务层独立出来,置于底层的领域模型或者表模块之上。通常只有使用领域模式或者表模块时才会这样细分,因为使用事务脚本的领域层产不复杂,没有必要再单独设服务层。表现逻辑与领域层的交互完全通过服务层。
最小化情况下,服务层只是一个外观,所有实际的行为都在下层的对象中,服务层所做的只是将上层调用传递到更低层。
另一个极端则是将大多数业务逻辑都以事务脚本的形式置于服务层中,下层的服务对象变得极为简单,如果下层是领域模型,则其中的对象与数据库一一对应,因而可以使用诸如活动记录等较简单的数据源层。
折中方式是控制器-实体风格,将单个事务或用例所特有的逻辑置于事务脚本之中,它们通常被称为控制器或者服务。
4、映射到关系数据库
4.1 架构模式
把sql访问从领域逻辑中分离出来,并把它放到独立的类中。让它们以数据库中的表结构为基础 ,每一个数据库表对应一个类,为数据库表建立一个入口。
使用入口的方式有两种,行数据入口和表数据入口。对于提供了记录集的,在使用记录集时,对数据库中的每个表,仅仅需要一个对象来管理,这种方式就是表数据入口,成为使用表模块的当然选择。
在简单应用中,领域模型是一种和数据库结构相当一致的简单结构,对应每个数据库表都有一个领域类,在复杂度适中情况 下,可以使用活动记录,让每个领域负责数据库的存取过程。
在复杂情况下,让间接层完成领域对象和数据库表之间的映射,即数据映射器处理数据库和领域模型之间所有的存取操作,并且允许双方都能独立变化,这个是数据库映射架构中最复杂的架构,好处是把两个层完全独立。
4.2 行为模式
就是如何让各种对象从数据库表中读取出来以及存到数据库中。一个客户对象可以拥有加载和保存方法来进行这项工作,用活动记录模式是一种显而易见的方式。
在涉及到多个记录及操作以及一致性问题时,考虑使用工作单元模式,会跟踪所有从数据库中读取的对象以及所有以任何形式修改过的对象。同样负责将更新提交到数据库。
工作单元是一个对象,充当数据库映射的控制器,就是来源于把数据库映射控制器的行为分解到它自己的对象中。
加载对象时,就避免把同一对象加载两次,可以在标识映射里记录读取的第一行,每次读入数据时,到标识映射里检查是否已经存在,如果已经加载,则返回一个对它的引用,所有更新操作就可以正确地组织好,还可以得到一些好处,比如可以避免一些数据库调用,因为标识映射就像一个数据高速缓存,不过标识映射的主要目的是保持一致性,而不是提高性能。
使用领域模型时,必须合理安排,使得关联的对象一起加载,如果许多对象都是连接在一起的,则读取任何对象都会从数据库中带出大批的对象,为了避免这种低效,必须设法减少带出来的东西,可以使用延迟加载的方式。
4.3 读取模式
把读取数据的方法看作一个查找器。什么地方放置查找器方法由使用的接口模式来决定。分为基于行的和基于表的两种方式。如果是基于表的,能把插入和更新操作也捆绑在查找器方法中。如果是基于行的,这种情况就不行。用基于行的类可以使查找变成静态操作,但是这样就会使数据库操作不可替代,为了避免这个问题,最好是创建独立的查找器对象,每一个查找器类都有很多封装了sql语句的方法,当执行查找操作的时候,查找器对象返回一个适当的基于行的对象集合。
尽量一次读回多行
另一个避免多次进入数据库的方法是使用联接。使用联接操作,必须优化到在一次查询中处理3-4个联接。
4.4 结构映射模式
4.4.1 关系映射
通过对象中的一个标识域来保持每个对象的关系特性,并且通过查找这些值来保持对象引用和关系键之间的相互映射。
4.4.2 继承
单表继承是将父子类的中的属性全部定义到一个表中
具体表继承是每个类一个表,子表中包含父表中的属性
类表继承是每个类一个有,子表中只包含子类中的属性。
4.5 建立映射
自己选择数据库方案 | 事务脚本或者表模块 | 活动记录 |
领域模型 | 数据映射器 | |
存在一个数据库方案 | 事务脚本或者表模块 | 表数据入口或者行数据入口 |
领域模型 | 数据映射器 |
4.6 使用元数据
元数据映射基于把映射浓缩到元数据文件的方法。元数据文件详细描述数据库的列如何遇到到对象的域。
当使用元数据映射的时候,有必要构造对内存对象的查询。一个查询对象允许根据内存对象和数据来构造查询。查询对象可以使用元数据映射把基于对象域的表达式翻译成对应的sql。
一直使用这种方法就可以建立一个资源库,能在很大程度上从视图隐藏数据库,任何到数据库的查询都可以做成资源库基础上的查询对象。
4.7 数据库连接
建立连接的开销相当大,需要建立一个连接池。
为了保证在任何需要的地方都能得到一个连接,有两种选择,一是把这个连接作为一个直接的参数传递出去。另外一种是注册表,因为不希望多个线程使用同一个连接,需要一个线程范围内的注册表。
连接关闭有两种方式,一种是主动关闭,一种是通过垃圾回收自动关闭。
5、Web表现层
5.1 视图模式
有三种模式:转换视图、模板视图和两步视图
有两种选择:第一,使用转换视图还是模板视图,第二,使用一个阶段还是两个阶段
模板视图允许你在网页的结构中编写表现层,并允许在网页中嵌入标签,用以指明网页中的动态内容需要导向到哪里。优点:提供强大的功能和灵活性。缺点:代码混乱,难以维护。
转换视图使用程序的一种转换风格。常见的例子是XSLT,如果领域数据是以XML格式存在的,或者很容易转换成这种格式 ,那么使用转换视图将是非常有效的。
单阶视图通常在应用程序中为每一个屏幕都准备一个视图组件。视图提取领域数据并把它返回到HTML网页中。
两阶视图把这一过程分解成两个阶段:由领域数据产生一个逻辑屏幕,然后把它发送到HTML网页中。其中,每一个屏幕都有一个第一阶段的视图,而整个程序中只有一个第二阶段的视图。优点:可以决定把什么样的html网页用在一个地方。
5.2 输入控制器模式
有两种模式:页面控制器、前端控制器
页面控制器是为web站点上的每一个页面都准备一个输入控制器,可能就是一个包含视图功能和输入控制器功能的服务器页面本身。
前端控制器是把分离的思想贯彻得更彻底一些,通过单个对象处理所有的请求消息,这个单一的处理程序解释url来计算出它正在处理哪种类型的请求消息,然后创建一个分离的对象来处理它,通过这种方法,可以用一个单一的对象处理所有的HTTP页面。
6、并发
6.1 并发问题
更新丢失:两个同时操作,一个后开始但先完成,后完成的会覆盖。
不一致读:两次读,在第一次读后,有另外一个有添加数据,在第二次读时导致两次读的数据不一致
6.2 执行语境
请求:对应于软件工作的外部环境发出的单个调用,针对 这个调用,处理请求的软件会决定是否返回一个应答
会话:客户端和服务端之间一次长时间的交互,它可以只是一个单独的请求,但通常是由一系列用户认为逻辑上有关联的请求构成的。一次会话一般从用户登录开始,然后用户进行各种操作,包括提交查询和完成一个或多个业务事务,在会话结束的时候,用户退出,或者用户可以直接离开,并且假设系统会将用户的离开解释为退出。
事务:将多个请求当作单个请求来看待。
6.3 隔离与不变性
并发问题解决方案:隔离和不变性
6.4 乐观并发控制和悲观并发控制
并发控制策略:乐观并发控制和悲观并发控制
悲观锁:缺点是减少了并发程度。
乐观锁:缺点是在提交的时候才会发现冲突。
选择标准:冲突的频率与严重性,如果冲突少,或者后果不严重,应该选择乐观锁,可以得到更好的并发性。如果冲突的结果对于用户来说是痛苦的,需要使用悲观锁策略。
6.4.1 避免不一致读
可以使用悲观锁策略来避免不一致读。
乐观锁通常将冲突检测建立在数据的某种版本标记上,可能是时间戳,也可能是顺序计数器。可以通过核对将要更新数据的版本标记和共享数据的版本标记检测到更新丢失。不一致读也可以检测到。
6.4.2 死锁
悲观锁可以会导致死锁。
死锁处理方式:
一种 是使用软件来检测死锁的发生,在这种情况下,需要选择一个牺牲者,放弃他的工作和他所加的锁。
一种方式是给每一个锁都加上时间限制,一旦到达时间限制,所加的锁就会失效。
6.5 离线并发控制的模式
首选 乐观离线锁,缺点是在提交时才发现冲突。
悲观离线锁,可以尽早地发现错误,会降低系统的灵活性。
粗粒度锁,允许以一组对象为单位管理并发。
隐含锁,可以不用直接管理锁。
7、会话状态
7.1 存储会话状态模式
客户会话状态:在客户端保存数据,在web应用中使用cookie
服务器会话状态:在各次请求之间把数据放在内存中,也可以把数据以序列化对象的方式更长久地存放起来。
数据库会话状态:在服务器端存储 ,把会话数据分解成多个表和域,并把它保存在数据库中。
8、分布策略
分布对象设计第一定律:不要分布使用对象
8.1 远程接口和本地接口
本地接口最好的细粒度接口
远程接口应该使用粗粒度接口,为了减少方法调用次数提高性能。
8.2 分布边界
在设计系统时必须尽可能限制分布边界,但在必要的地方还得考虑它们。
在一个进程内使用细粒度对象进行设计,在分布边界上放置粗粒度的对象,目的是去提供一个细粒度对象的远程接口。粗粒度对象实际上不做任何事情,从而充当细粒度对象的外观,即远程外观。
远程外观一起使用的还有数据传输对象。
进行分布的另一方式是使用代理在进程间迁移对象,这个思想用到了延迟加载方案来传递对象。
9、领域逻辑模式
9.1 事务脚本
使用过程来组织业务逻辑,每个过程处理来自表现层的单个请求。
9.1.1 运行机制
使用事务脚本时,领域逻辑主要通过系统所执行的事务来组织。应当使用合理的方式将代码模块化。
事务脚本置于何处处取决于你如何组织你的软件层次,它可能会位于服务器页面,cgi脚本或分布式会话对象中。尽可能分离事务脚本,至少应当将它们放在不同的子程序中,更好的方法则是将它们置于与其他处理表现层和数据源层的类相独立的类中。不要让事务脚本调用任何表现层逻辑。
有两种方法来把事务脚本组织成类。最常用的方法是将数个事务放在一个类中,每个类围绕一个主题相关的事务脚本组织在一起。另一种方法是每一个事务脚本对应一个类,此时需使用命令模式。这种情况 下就定义一个所有命令的父类,在父类中声明事务脚本逻辑适合的执行方法。
将事务脚本组织成类的 优点:允许以对象的方式来操控脚本类的实例。
9.1.2 使用时机
在业务逻辑比较简单时使用。当业务逻辑越来越复杂时,使用这种模式难以保持良好的设计,特有的问题是事务之间的冗余代码。
9.2 领域模型
合并了行为和数据的领域的对象模型。
9.2.1 运行机制
使用领域模型需要建立一个完整的由对象组成的层,来对目标业务领域建模。数据和处理一般整合在一起,使得数据和数据之上的操作紧密聚合。
领域模型有两种风格,简单领域模型中,每一个数据库表与一个领域对象对应。复杂领域模型使用继承、策略和其他设计模式,是一张由互联的细粒度对象组成的复杂网络。复杂领域模型更适合于复杂的逻辑,但它到数据库的映射比较困难。简单领域模型可以使用活动记录,而复杂领域模型需要使用数据映射器。
领域模型与系统中其化层之间的耦合程序应达到最小,你会发现许多分层模式,它们的主导思想就是在领域模式与系统中其他部分之间保持尽可能小的依赖。
9.2.2 使用时机
何时使用这一模型完全取决于系统中行为的复杂程序。如果你的业务规则复杂多变,涉及校验、计算、衍生,你就应该利用对象模型进行处理。反之,如果你只有一些简单的判空值和少量的求和计算,事务脚本会是一个更佳的选择。
影响选择的因素之一是开发小组灵活运用领域对象的程度。学会如何设计和使用领域模型是极为重要的。
使用领域模型时,首选 的数据库交互方式是数据映射器。它将有助于保持领域模型对数据库的独立性。
使用领域模型时,可能会考虑设立一个服务层,以便给领域模型一个更清晰的api。
9.3 表模块
处理某一数据库表或视图中所有行的业务逻辑的一个实例。
表模块以一个类对应数据库中的一个表来组织领域逻辑,而且使用单一的类实例来包含将对数据进行的各种操作程序。它与领域模型的主要区别在于,如果你有许多订单,领域模型对每一个订单都有一个对象,而表模块则只用一个对象来处理所有订单。
9.3.1 运行机制
表模块的长处是允许你将数据与行为封装在一起,同时又可以充分利用关系数据库的特点,表面上看起来表模块与常规的对象很相似,关键区别在于它本身没有标识符来标出它所代表的实体对象。
最常见的表模块例子是对数据库中的每个表使用一个表模块。
表模块可以是一个实例,也可以是一个静态方法的集合。使用实例的优点是允许你将表模块初始化,使它与某个已存在的记录集联系起来,该记录集可能是某个查询的返回结果。然后,你就可以使用 这个实体来处理记录集中的数据行。实例同样使得继承的运用成为可能,这样我们通过在常规合同的基础上增加一些额外的行为,可以很快写出一个急需的合同模块。
表模块可以包括查询作为工厂方法。另一种方法是使用一个表数据入口,但其缺点在于:在设计中需要一个额外的表数据入口类和相应的机制。优点是你可以在来自不同数据源的数据上使用单个表模块。
9.3.2 使用时机
在很大程度上依赖于以表方式组织的数据,因此当你使用记录集存取表数据时应当使用这一模式。
表模块并没有提供完全的面向对象能力来组织复杂的领域逻辑,你不能在实例之间建立关联,而且多态机制也无法工作良好。当处理复杂逻辑时,领域模型是一个好的选择。实质上,你不得不在领域模型与表模块之间权衡,要么选择前者处理复杂逻辑的能力,要么选择后者较易与下层面向表的数据结构整合的能力。
10、数据源架构模式
10.1 表数据入口
充当数据库表访问入口的对象,一个实体处理表中所有的行。
表数据入口 包含了用于访问单个表或者视图的所有的sql。其他代码调用它的方法来实现所有与数据库的交互。
10.1.1 运行机制
表数据入口接口很简单,一般包括几个从数据库中获取数据的查找方法以及更新、插入和删除方法。
表数据入口它是如何从查询返回信息,一种方法是返回某种简单数据结构。一种更好的方法是采用数据传输对象。
大多数使用表数据入口的时候,对数据库中的每一张表使用一个入口,但是对非常简单的情况 ,可以只用一个表数据入口处理所有表的全部方法。
10.1.2 使用时机
首先决定是否采用入口方法,然后决定用哪一个。
表数据入口 很好地映射到数据库或记录类型上去。也为封装对数据源的精确访问逻辑提供了一种自然的方法。
表数据入口 可以同表模块一起使用,产生一个记录集数据结构由表模块处理。
表数据入口特别适用于事务脚本。
10.2 行数据入口
充当数据源中单条记录入口的对象,每行一个实例。
行数据入口提供了与记录结构中记录相似的对象,所有对数据源的访问细节都隐藏在这个接口之后。
10.2.1 运行机制
行数据入口是和单条记录极其相似的对象,一般能实现从数据源类型到内存中类型的任意转换。是每一数据行的良好接口,尤其适用于事务脚本 。
在哪里存放产生该模式的查找操作?可以选择静态查找方法,但是不支持需要为不同数据源提供不同查找方法的多态。在此情况下有必要设置单独的查找方法对象。
行数据入口和活动记录之间很相似,难以区别。关键是要看是否存在任何领域逻辑。如果存在,则是活动记录。行数据入口 仅包含数据访问逻辑而没有领域逻辑。
行数据入口写起来冗长,但是对基于元数据映射的代码生成,是一种很好的选择。
10.2.2 使用时机
使用事务脚本时,经常使用行数据入口。
当行数据入口从元数据自动生成,数据映射器由手动实现时,行数据入口和数据映射器一起配合使用。
10.3 活动记录
一个对象,它包装数据库表或者视图中某一行,封装数据库访问,并在这些数据上增加了领域逻辑。
活动记录把数据访问逻辑置于领域对象中。
10.3.1 运行机制
活动记录本质是领域模型,这个领域模型中的类和基数据库中的记录结构十分吻合。每条活动记录负责向数据库保存数据,从数据库加载数据以及处理作用于数据之上的领域逻辑。
活动记录的数据结构应该与数据库中的结构完全匹配。
10.3.2 使用时机
适用于不太复杂的领域逻辑
10.4 数据映射器
在保持对象和数据库彼此独立的情况下,在二者之间移动数据的一个映射器层。
10.4.1 运行机制
映射层有在对内存对象影响最小的情况下使用延迟加载。
10.4.2 使用时机
通过与领域模型一起使用。
11、对象关系行为模式
11.1 工作单元
工作单元记录在业务事务过程中对数据库有影响的所有变化,操作结束后,作为一种结果。工作单元了解所有需要对数据库做的改变。
11.1.1 运行机制
工作单元记录这些变化的对象,只要开始做一些可能会对数据库有影响的操作,就创建一个工作单元去记录这些变化。每当创建、改变或者删除一个对象时,就通知此工作单元。也可以让此单元知道所读过的对象,通过验证在整个业务事务处理过程中数据库中的所有对象都没有改变,从而检查不一致读。
工作单元的关键是在提交的时候,它决定要做什么,它打开一个事务,做所有的并发检查并向数据库写入所做的修改。
工作单元需要知道它应该记录哪些对象,可以由调用者实现(调用者注册方式),也可以让发生变化的对象通知工作单元 (对象注册方式)。
工作单元控制器是工作单元在读取操作的时候将产生一个拷贝,在提交时比较当前对象和拷贝对象,看对象是否发生了变化,对那些真正改变了的域进行有选择的更新,也可以避免在对象域内执行注册调用 。折中方式是只拷贝改变了的对象,这需要注册,但是支持有选择的更新。因此当读操作大超过写操作时,还会极大地降低拷贝操作的负担。
工作单元的另一个用武之地是当数据库使用引用 完整性时,用它来保证更新顺序。
工作单元还可用于处理批量更新。
11.1.2 使用时机
用于记录操作过的对象,以便知道为了将内存数据同步到数据库时需要考虑哪些对象。
工作单元的强大功能是把所有的信息保存在一个地方,一旦使用了工作单元 ,就不必为记录所做的修改做很多操作,而且,还可以作为更复杂情况下的固定处理平台。
11.2 标识映射
标识映射记录在一个业务事务中从数据库读出的所有对象,无论什么时候要用一个对象,先检查标识映射,看需要的对象是否已经存在其中。
11.2.1 运行机制
最基本的思想是使用一系列映射,映射包含了从数据库读出的对象。
- 键选择
- 显式的还是通用的。显示标识映射为每一种需要的对象提供不同方法。通用映射为所有的对象提供统一的方法,而用一个参数指出所需的对象类型。
- 数量。一个类对应一个映射,整个会话对应一个映射。
- 标识映射放到哪里。放到特定会话的对象中。如果使用工作单元,标识映射放到工作单元中。
11.2.2 使用时机
主要是不希望出现两个内存对象对应同一条数据库记录的情形。另一个用途是作为数据库读取操作的高速缓存。
不变对象用不着标识映射。
对依赖映射不需要建立标识映射。
标识映射有助于避免同一个会话中的更新冲突,但是对超出会话的冲突根本不起作用。
11.3 延迟加载
延迟加载会暂时中止这个加载过程,方法是在对象结构中设立一个标志以便使需要的数据在用到时才被加载。
11.3.1 运行机制
延迟加载的方法:延迟初始化、虚代理、值保持器和重影
延迟初始化:每次访问属性域都要先检查该域是否为空,如果为空,在返回值之前计算出这个域的值。
虚代理:一个对象,但是不包含任何东西,只有当它的一个方法被调用时,它才会从数据库加载对象。
值保持器:用来包装某个其他对象的对象,要想获得基对象,可以访问值保持器得到它的值,只有第一次访问值保持器时它真正从数据库读取数据。
重影:部分状态下的真实对象。当从数据库加载对象的时候,它只包含其id,当每次要访问某个域时,它就会加载其完全状态。
11.3.2 使用时机
取决于加载一个对象时需要从数据库读取多少数据和数据库调用的次数。只有在域需要另外的数据库访问时才考虑使用延迟加载
12 对象关系结构模式
12.1 标识域
为了在内存对象和数据库行之间维护标识而在对象内保存的一个数据库标识域。
12.1.1 工作机制
选择无意义的键(有意义的键不可信)
简单键和组合键
表唯一键(在一张表中是唯一的)和数据库唯一键(对任何一个表的任何一个数据行都是唯一的)
对象内标识域的表示:简单键是使用基础类型就可以,组合键需要使用一个键类。
取得新键:自动生成域,数据库计数器,GUID,自定义策略生成键值,键表。
12.1.2 使用时机
内存对象与数据库行之间存在映射关系时使用标识域,在使用领域模型或者行数据入口情况下。
另一种方法是通过扩展标识映射来维护标识域的对应关系。
12.2 外键映射
把对象间的关联映射到表单的外键引用
12.2.1 运行机制
对于一对多情况时,更新方式有三种方式:(1)删除和插入(2)加入一个后向指针(3)区分对象集
对于第一种情况,缺点是在依赖映射的情况下才可以这么做,不能在此之外被引用。
对于第二种情况,可以使用处理单值域的方式处理更新。
对于第三种情况,可以通过数据库的当前状态或者第一次读取的数据来区分。数据库区分是指从数据库中重样的读出对象集合,与当前对象集合进行比较。第一次读取的数据来区分是指需要保存读取的数据。
12.2.2 使用时机
外键映射适用于类间几乎所有的关联,不适用情况是多对多关联。
如果有一个没有后向指针的集合域,应该考虑多的那一边是否应该是依赖映射。
如果相关对象是一个值对象,就应该使用嵌入值。
12.3 关联表映射
把关联保存为一个表,带有指向表的外键。
12.3.1 运行机制
基本思想是使用一个链接表来保存这种关联关系。这个表仅仅含有两个相互关联的表的外键id,对于每一对相关联的对象,都有一个数据行与之对应。
链接表没有相对应的内存对象。
12.3.2 使用时机
适用一个多对多关联关系。
12.4 依赖映射
让一个类为部分类执行数据库映射
12.4.1 运行机制
基本思想是在数据库持久化时,数据库中的某个类依赖于其他类。每个依赖者有且只有一个所有者。
在uml模型中,使用组合来表示所有者也依赖者之间的关系。
12.4.2 使用时机
当有一个对象只被另一个对象引用 的时候,可以使用依赖映射。此时必须满足:
- 每个依赖都必须只有一个所有者
- 不能有任何除所有者之个的对象拥有对依赖者的引用 。
12.5 嵌入值
把一个对象映射成另 个对象表的若干字段。
12.5.1 运行机制
当所有者对象被加载或保存时,依赖者对象也同时被加载或保存,依赖者类没有自己的持久化方法。
12.5.2 使用时机
适用于简单的值对象。
12.6 序列化LOB
通过将多个对象序列化到一个大对象中保存一个对象图,并存储在一个数据库字段中。
12.6.1 运行机制
序列化方式:二进制形式或者文本字符形式。
12.6.2 使用时机
突出对象模型。
12.7 单表继承
将类的继承层次表示为一个单表,表中的各列代表不同类中的所有域。
单表继承把一个继承结构中所有类的所有域都映射到一个单表中。
12.7.1 运行机制
有一个表包含某个继承层次中所有类的所有数据。每个类负责把与之相关的数据保存在表的一行中。
加载时,必须知道实例化哪个类。为此,数据表中有一个域用来指示应该使用哪个类。代码域需要用一些代码来翻译才能映射到对应的类。当新的类加入到这个继承层次的时候,这段代码就需要扩展。如果把类名嵌入到表里,那么就可以直接使用这个名字来实例化。
加载数据的时候要首先读代码,看看需要实例化哪个类。保存数据的时候代码需要由层次关系中的超类写出。
12.7.2 使用时机
优点:
- 在数据库中只需要关注一个表
- 获取数据时不必进行连接操作
- 任何对继承层次的重构都不需要修改数据库
缺点:
- 数据库表中的列有时对应到对象域,有时却没有相应的域与之对应,会使直接使用数据库表的用户感到迷惑。
- 只被某些子类使用的列会带来数据库空间的浪费。
- 单表可能最终太大了,有许多索引并被频繁上锁,从而导致访问该表时效率低下。
- 所有地域只有一个名字空间,必须保证在不同的域不能使用相同的名字。
12.8 类表继承
用每个类对应一个表来表示类的继承层次。
12.8.1 运行机制
领域类中的域直接映射到相应表中的字段。
将对应的数据库表的行链接起来的方式:
- 使用公用的主键值。主键必须在各表之间都是唯一的。
- 每个表都有自己的主键并且使用超类表的外键把各行联系在一起。
如何用有效的方式把数据从多个表中取回?
在多个表中执行一次连接操作,然而受限于数据库的优化方式,在3-4个表上进行表连接操作就会使处理速度慢下来。
具体需要连接哪些表?
如果是查找父类,为了在某些表没有数据的时候有效地连接,需要使用外连接。
12.8.2 使用时机
优点:
- 所有列对每一行都是相关的,没有浪费空间
- 领域模型和数据库之间的关系明了。
缺点:
- 为了加载一个对象需要访问 多个表,意味着需要进行一次连接操作,或者进行多次查询并且在内存中将查询结果合并
- 字段在层次中任何上下移动的重构都会导致数据库更改
- 超类表可能会成为瓶颈
- 高度的规范化使特殊查询对于理解。
12.9 具体表继承
用每个具体类对应一个表表示类的继承层次。
12.9.1 运行机制
每个表中的列都对应着具体类和这个具体类的所有祖先类。超类中的任何域在子类的表中都会被复制。保证键不仅在一个表中是唯一的,而且在继承层次里所有表中都是唯一的。
12.9.2 使用时机
优点:
- 每个表都是自包含的,并且没有不相关的域。
- 从具体映射器读取数据的时候不需要连接操作
- 只有在类被访问的时候表才被访问,这样可以分散访问负载
缺点:
- 主键难处理
- 不能把数据库关系加到抽象类中。
- 如果领域类中的某个域被提升到上一层或放置到下一层,就必须更改表定义。
- 如果一个超类中的域改变了,就需要改变每一个拥有这个域的表
- 超类中的一次查找需要检查所有表。
12.10 继承映射器
用来组织可以处理继承层次的数据库映射器的一种结构。
12.10.1 运行机制
采用层次方式来组织映射器,这样每个领域类都有一个映射器来为它保存和加载数据。
在超类中定义模板,在具体子类中实现具体细节。
12.10.2 使用时机
适用于任何基于继承的数据库映射
13、对象关系元数据映射模式
13.1 元数据映射
在元数据上保持关系-对象映射的详细信息
13.1.1 运行机制
代码生成和反射编程
13.1.2 使用时机
元数据映射大大减少数据库映射所需的工作量。
13.2 查询对象
描述一次数据库查询的对象。
查询对象是一个解释器,由多个对象组成的结构,可以自身转化为一个sql查询。
13.2.1 运行机制
查询对象是解释器模式在表示sql查询上的应用。它的主要作用是使客户可以构造各种类型的查询,并把这些对象结构转换成适当的sql字符串。
查询对象的一个变种是允许用一个样例领域对象来指明一次查询。
13.2.2 使用时机
在使用领域模型和数据映射器时,才会需要查询对象,还需要元数据映射使用查询对象。
13.3 资源库
协调领域和数据映射层,利用类似于集合的接口来访问领域对象。
13.3.1 运行机制
资源库用一个基于规约的对象选择方法来取代基于数据映射器类的特殊的查找器方法。
13.3.2 使用时机
资源库改进了规约模式,可以删除所有对特例建立查询对象的代码。
14、web表现模式
14.1 页面控制器
在web站点上为特定页面或者动作处理请求的对象。
页面控制器在web站点上为每一个逻辑页面都准备了一个输入控制器,这个输入控制器可能是页面本身 ,也有可能是一个对应这个页面的单独对象。
14.1.1 运行机制
控制器绑定在每一个动作上。
页面控制器的基本责任:
- url解码并获取所有必要的数据
- 创建和调用模型对象来处理数据
- 决定哪个视图将用来显示结果页面,并且把模型的相关信息传送给它。
14.1.2 使用时机
页面控制器在站点只拥有简单控制器逻辑的时候工作得很好。
14.2 前端控制器
为一个web端点处理所有请求的控制器。
14.2.1 运行机制
前端控制器处理一个web端点的所有调用,通常由两部分组成:一个 web处理程序和一个command层次结构。
14.2.2 使用时机
优点是允许你提炼出重复代码。
14.3 模板视图
通过在html页面中嵌入标记向html发送消息。
14.3.1 使用时机
通过观察网页的结构可以组合网页的内容。
缺点:
- 网页很容易被插入复杂的逻辑,导致网页难以维护
- 模板视图比转换视图难测试
14.4 转换视图
一个视图,一项一项地处理领域数据,并且把它们转换成html
14.4.1 运行机制
核心思想是写一个查看面向领域的数据并将其转换成html内容的程序 。
转换视图与模板视图不同点是组织视图的方式不同,模板视图的组织是围绕输出的,转换视图的组织是围绕为每种输入元素所准备的单独转换而形成的。
14.4.2 使用时机
取决于工具选择。
14.5 两步视图
用两个步骤来把领域数据转换成html:第一步,形成某种逻辑页面。第二步,把这些逻辑页面转换成html页面。
14.5.1 运行机制
第一阶段,把信息装配到一个逻辑屏幕结构 中,该结构可以作为显示元素的参考,但不包含html。职责有以下三个:
- 去访问一个面向领域的模型
- 为屏幕提取相关信息
- 把这个信息放入面向表现的结构中
第二阶段,获得面向表现的结构 ,并把它放入 html中。
建立两步视图方法:两步XLST,使用类
14.5.2 使用时机
分离开第一和第二阶段,可以容易地进行全局性的改变。
15、分布模式
15.1 远程外观
为细粒度对象提供粗粒度的外观来改进网络上的效率。
一个远程外观是一个粗粒度的外观,建立在大量的细粒度对象之上。所有细粒度接口都没有远程接口,并且远程外观不包括领域逻辑。远程外观所要完成的功能是把粗粒度的方法转换到低层的细粒度对象上。
15.1.1 运行机制
远程外观解决了这种方法中的分布式问题,并且已经成为解决这种问题的标准模式。细粒度对象非常适合解决复杂的逻辑。任何复杂的逻辑都可以放到细粒度的对象中,设计这些细粒度对象是为了在一个进程内进行协作。为了有效的对它们进行远程访问,使用一个单独的外观对象作为远程接口。
在更复杂的情况中,单个远程外观可以 充当许多细粒度对象的一个远程入口。
远程外观考虑粒度的大小。倾向于粗粒度的结构和较少的远程外观。对一个中等大小的应用程序来说,可以使用一个远程外观,甚至对于一个大型的应用程序,也只会有6个左右的远程外观。意味着每个远程外观都包含许多的方法。
远程外观的设计都是基于特定客户的需要,大部分的需要都是希望通过用户界面来观看和更新信息。在这种情况下,用单个远程外观服务于屏幕。外观的设计就是要使外部用户的使用简单化,而不是为了简化内部系统。
远程外观有有状态和无状态之分。无状态的远程外观可以组成池,这样就可以提高资源的利用率和效率。使用有状态的远程外观时,远程外观必须维持每一个状态,可能通过实现服务器会话状态来很容易地完成。
远程外观同样也提供安全检查,事务控制。
不就把领域逻辑放在远程外观中。
远程外观所有的工作都围绕在一个简单的接口上。
会话外观的描述都包括把领域逻辑放入其中,尤其是工作流这种类型的逻辑。
服务层不需要远端的,因此不需要粒粒度方法。为了简化领域模型,动作通常终止于粗粒度的方法上,这是为了使结构更为清晰而不是为了网络的效率,而且,服务层不需要使用数据传输对象,通常只返回领域对象。
如果一个领域模型同时在进程内和远程使用,可以有一个服务层并在 这个服务上分出一个单独的远程外观层。如果进程只是在远程执行,那么这种方法将会很容易把服务层转换成远程外观,并且在服务层中不包含任何应用逻辑。如果服务层中包含了一些应用逻辑,那么使远程外观成为一个单独的对象。
15.1.2 使用时机
当需要远程访问细粒度对象模型的时候,应该使用远程外观。
这种模式常用的地方是在表现和领域模型之间。
15.2 数据传输对象
一个为了减少方法调用次数而在进程间传输数据的对象。
创建一个数据传输对象,这个对象保留所有调用需要用到的数据,需要被序列化以便能在连接中传输,通常,在服务器方使用一个组装器,负面在dto和任何领域对象之间传输数据。
15.2.1 运行机制
数据传输对象不仅仅包含一个服务器对象。它从所有的服务器对象中集结所有远端对象可能需要的数据。
用不同的数据传输对象很容易看清楚在每次调用中传输了什么数据,会产生大量的数据传输对象。单一数据传输对象会减小编码的工作量,但是很难掌握每次调用所传输的数据。
请求方和发送方是用一个数据传输对象还是不同的数据传输对象,如果数据在双方是非常一致的话,使用单一的数据传输对象,如果双方的数据不同,为请求方和发送方准备一个数据传输对象。
数据传输常见格式是记录集,它是一系列的表格记录。另外一种格式是集合数据结构,不推荐使用数组,数组的索引使程序编得更晦涩,最好使用字典,将失去清晰的接口和强类型所带来的好处。
序列化方式
领域对象与数据传输对象的相互映射。
15.2.2 使用时机
在一个方法调用中在两上进程之间传输多个数据项时,应使用数据传输对象。
数据传输对象模式可选的替代方案:
- 不使用任何对象,简单地使用一个有着多个参数的set方法,或者使用一个有着若干按引用传递的参数的get方法。
- 直接使用某种字符串表示形式,而非使用对象作为其接口。
16、离线并发模式
16.1 乐观离线锁
通过冲突监测和事务回滚来防止并发业务事务中的冲突。
16.1.1 运行机制
为每条记录关联一个版本号。锁的检查是通过在所有地更新或删除记录的sql语句中加上对版本号的差别来完成的,用一条sql语句就可以同时获取锁和更新数据。最后的一步是检查业务事务执行的sql语句所返回的行数。
除了增加记录的版本号,可以再加上一些诸如谁在何时最后修改了一条记录等信息,以便对冲突进行管理。
另外一种方式是在进行更新的sql语句中的where子句包含对所有字段的检查。好处是不用在where语句中用到版本字段,可以在无法为数据库表加上一个版本字段的情况下使用。问题在于会导致update语句后跟上很长的where语句。
16.1.2 使用时机
用于业务事务冲突率低的情况。
16.2 悲观离线锁
每次只允许一个业务事务访问数据以防止并发业务事务中的冲突。
16.2.1 运行机制
独占写锁
独占读锁
读写锁
16.2.2 使用时机
在冲突率很高的并发会话中。
16.3 粗粒度锁
用一个锁锁住一组相关对象。
16.3.1 运行机制
为一组对象建立一个控制点,只需要一个锁就可以锁住一堆对象。
16.3.2 使用时机
获取和释放锁的代价很小。
16.4 隐含锁
允许框架或层超类型代码来获取离线锁。
16.4.1 运行机制
在应用框架中完成那些绝对不能跳过的锁机制。
16.4.2 使用时机
适用于有框架中