The DoYouHaoBaby PHP Framework中文开发文档手册
Copyright(c) DoYouHaoBaby.NET All Rights Reserved.
[ 聚合 ]

领域对象在它们的生命期内会历经若干种状态,直到最后消亡。有时它们会被保存到一个永久的位置,例如数据库中,
这样可以在以后的日子里被检索到,或者被存档。有时它们会被完全从系统中清除掉,包括从数据库和归档介质上。

管理领域对象的生命周期自身就会遇到一个挑战,如果做得不恰当,就会对领域模型产生一个负面的影响。我们将引入3 个模式来
帮助我们处理这个挑战。聚合是一个用来定义对象所有权和边界的领域模式。工厂和资源库是另外的两个设计模式,用来帮助我们处
理对象的创建和存储问题。

我们将从聚合开始讨论。一个模型会包含众多的领域对象。不管在设计时做了多少考虑,我们都会看到许多对象会跟其
他的对象发生关联,形成了一个复杂的关系网。这些关联的类型有很多种。对模型中的每个可导航的关联而言,都应该有
对应的软件机制来强调它。领域对象间实际的关联在代码中结束,有时甚至却在数据库中。

客户和用它名字开立的银行账户之间存在的一个1 对1 的关系会被表现为两个对象之间的引用,并且在两个数据库表中隐含着一个关联关系,一个表存放客户
信息,另一个表存放账户信息。来自模型的挑战是通常不让它们尽量完整,而是让它们尽量地简单和容易理解。这意味着,直到模
型中嵌入了对领域的深层理解,否则就要时常对模型中的关系进行消减和简化。

一个1 对多的关联关系就更复杂了,因为它涉及到了相关的多个对象。这种关系可以被简单转化成一个对象和一组其他对象之间的一个关联,虽然这并不总能行
得通。

多对多的关联关系大部分情况下是双向的。这又增加了复杂度,使得对这样的对象的生命周期管理变得困难。关联的数字应该被尽可
能消减。首先,要删除模型中非本质的关联关系。它们可能在领域中是存在的,但它们在我们的模型中不是必要的,所以我们要清除
它们。

其次,可以通过增加约束的方式来消减多重性。如果很多对象满足一种关系,那么在这个关系上加入正确的约束后,很有可能
只有一个对象会继续满足这种关系。

第三,很多时候双向关联可以被转换成非双向的关联。每一辆汽车都有一个引擎,并且引擎在运转时,都会属于一辆汽车。这种关系是
双向的,但是很容易成为汽车拥有引擎,而不用考虑反向的。

在我们消除和简化了对象间的关联后,我们仍然会遭遇到很多的关系。一个银行系统会保留并处理客户数据。这些数据包括客户个人
数据(例如姓名、地址、电话号码、工作描述等)和账户数据:账户、余额、执行的操作等。当系统归档或者完全删除一个客户的信
息时,必须要保证所有的引用都被删除了。如果许多对象保有这样的引用,则很难确保它们全被清除了。同样,如果一个客户的某些
数据发生了变化,系统必须确保在系统中执行了适当的更新,数据一致性必须得到保证。这通常被提交到数据库层面进行处理。事务
通常用来确保数据一致性。但是如果模型没有被仔细地设计过,会产生很大程度的数据库争夺,导致性能极差。当数据库事务在这样
的操作中担负重要角色时,我们会期望直接在模型中解决跟数据一致性相关的一些问题。


通常也有必要确保不变量。不变量是在数据发生变化时必须维护的那些规则。这在许多对象与数据发生变化的对象保持引用时更难实
现。

在模型中拥有复杂关联的对象发生变化时,很难保证其一致性。许多时候不变量被应用到密切相关的对象,而不是离散的对象。但是
谨慎的锁定模式又会导致多个用户之间发生不必要的冲突,系统变得不可用。

因此,使用聚合。聚合是针对数据变化可以考虑成一个单元的一组相关的对象。聚合使用边界将内部和外部的对象划分开来。每个聚
合有一个根。这个根是一个实体,并且它是外部可以访问的唯一的对象。根可以保持对任意聚合对象的引用,并且其他的对象可以持
有任意其他的对象,但一个外部对象只能持有根对象的引用。如果边界内有其他的实体,那些实体的标识符是本地化的,只在聚合内
有意义。

聚合是如何保持数据一致性和强化不变量的呢?因为其他对象只能持有根对象的引用,这意味着它们不能直接变更聚合内的其他的对
象。它们所能做的就是对根进行变更,或者让根来执行某些活动。根能够变更其他的对象,但这是聚合内包含的操作,并且它是可控
的。如果根从内存中被删除或者移除,聚合内的其他所有的对象也将被删除,因为再不会有其他的对象持有它们当中的任何一个了。
当针对根对象的修改间接影响到聚合内的其他的对象,强化不变量变得简单了,因为根将做这件事情。如果外部对象能直接访问内部
对象并且变更它们时,这将变得越发困难。在这种情况下想强化不变量意味着讲某些逻辑放到外部对象中去处理,这不是我们所期望
的。

根对象可能将内部的临时引用传递给外部对象,作为限制,当操作完成后,外部对象不能再持有这个引用。一个简单的实现方式是向
外部对象传递一个值对象的拷贝。在这个对象上发生了什么将不再重要,因为它不会以任何方式影响到聚合的一致性。如果聚合对象
被保存到数据库中,只有根可以通过查询来获得。其他的对象只能通过导航关联来获得。聚合内的对象可以被允许持有对其他聚合的
根的引用。

根实体拥有全局的标识符,并且有责任管理不变量。内部的实体拥有内部的标识符。

将有关的实体和值对象放置到聚合中并且围绕它们定义边界。选择一个实体作为每个聚合的根,并且通过根来控制所有对边界内的对
象的访问。允许外部对象仅持有对根的引用。临时对内部成员的引用仅可以被传递给一个单独的操作使用。因为根控制了访问,将不
能盲目对内部对象进行变更。这种安排让强化聚合内对象的不变量变得可行,并且对聚合而言,它是一个处于任何变更状态的整体。
一个简单的聚合的案例如下图所示。客户是聚合的根,并且其他所有的对象都是内部的。如果需要地址,一个它的拷贝将被传递到外
部对象。