DDD读书笔记

总体导览图

在这里插入图片描述

分层模式

在这里插入图片描述

用户界面层(或表示层)负责向用户显示信息和解释用户指令。这里指的用户可以是另一个计算机系统, 不一定是使用用户界面的人
应用层定义软件要完成的任务,并且指挥表达领域概念的对象来解决问题。这一层所负 责的工作对业务来说意义重大,也是与其他系统的应用层进行交互的必要渠道 。应用层要尽量简单,不包含业务规则或者知识,而只为下一层中的领域对象协调 任务,分配工作,使它们互相协作。它没有反映业务情况的状态,但是却可以具有 另外一种状态,为用户或程序显示某个任务的进度
领域层(或模型层)负责表达业务概念,业务状态信息以及业务规则。尽管保存业务状态的技术细节 是由基础设施层实现的,但是反映业务情况的状态是由本层控制并且使用的。领域 层是业务软件的核心
基础设施层为上面各层提供通用的技术能力:为应用层传递消息,为领域层提供持久化机制, 为用户界面层绘制屏幕组件,等等。基础设施层还能够通过架构框架来支持4个层次 间的交互模式

软件中的模型

关联

对象之间的关联使得建模与实现之间的交互更为复杂。
模型中每个可遍历的关联,软件中都要有同样属性的机制。

至少有3种方法可以使得关联更易于控制。
(1) 规定一个遍历方向。
(2) 添加一个限定符,以便有效地减少多重关联。
(3) 消除不必要的关联。

例子:

像很多国家一样,美国有过很多位总统。这是一种双向的、一对多的关系。然而,在提到‚乔 治〃华盛顿‛这个名字时,我们很少会问‚他是哪个国家的总统?‛。从实用的角度讲,我们可 83 以将这种关系简化为从国家到总统的单向关联。如图5-1所示。这种精化实际上反映了对领域的 深入理解,而且也是一个更实用的设计。它表明一个方向的关联比另一个方向的关联更有意义且
更重要。也使得Person类不受非基本概念President的束缚。
在这里插入图片描述

通常,通过更深入的理解可以得到一个‚限定的‛关系。进一步研究总统的例子就可以知道, 一个国家在一段时期内只能有一位总统(内战期间或许有例外)。这个限定条件把多重关系简化 为一对一关系,并且在模型中植入了一条明确的规则。如图5-2所示。1790年谁是美国总统?乔 治〃华盛顿。
在这里插入图片描述

Entity

定义 : 很多对象不是通过它们的属性定义的,而是通过连续性和标识定义的。

一些对象主要不是由它们的属性定义的。它们实际上表示了一条“标识线”(A Thread of Identity),这条线跨越时间,而且常常经历多种不同的表示。有时,这样的对象必须与另一个具 有不同属性的对象相匹配。而有时一个对象必须与具有相同属性的另一个对象区分开。错误的标 识可能会破坏数据。

当一个对象由其标识(而不是属性)区分时,那么在模型中应该主要通过标识来确定该对象 的定义。使类定义变得简单,并集中关注生命周期的连续性和标识。定义一种区分每个对象的方 式,这种方式应该与其形式和历史无关。要格外注意那些需要通过属性来匹配对象的需求。在定 义标识操作时,要确保这种操作为每个对象生成唯一的结果,这可以通过附加一个保证唯一性的 符号来实现。这种定义标识的方法可能来自外部,也可能是由系统创建的任意标识符,但它在模 型中必须是唯一的标识。模型必须定义出“符合什么条件才算是相同的事物”。

在这里插入图片描述

比较
体育场座位预订程序可能会将座位和观众当作ENTITY来处理。在分配座位时,每张票都有一 个座位号,座位是ENTITY。其标识符就是座位号,它在体育场中是唯一的。座位可能还有很多其 他属性,如位臵、视野是否开阔、价格等,但只有座位号(或者说某一排的一个位臵)才用于识 别和区分座位。
另一方面,如果活动采用入场卷的方式,那么观众可以寻找任意的空座位来坐,这样就不需 要对座位加以区分。在这种情况下,只有座位总数才是重要的。尽管座位上仍然印有座位号,但 软件已经不需要跟踪它们。事实上,这时如果模型仍然将座位号与门票关联起来,那么它就是错 误的,因为采用入场卷的活动并没有这样的约束。在这种情况下,座位不是ENTITY,因此不需要 标识符。

需要关注设计标识操作

Value Object

定义: 很多对象没有概念上的标识,它们描述了一个事务的某种特征。

跟踪ENTITY的标识是非常重要的,但为其他对象也加上标识会影响系统性能并增加分析工 作,而且会使模型变得混乱,因为所有对象看起来都是相同的。
软件设计要时刻与复杂性做斗争。我们必须区别对待问题,仅在真正需要的地方进行特殊 处理。
然而,如果仅仅把这类对象当作没有标识的对象,那么就忽略了它们的工具价值或术语价 值。事实上,这些对象有其自己的特征,对模型也有着自己的重要意义。这些是用来描述事物 的对象。
用于描述领域的某个方面而本身没有概念标识的对象称为VALUE OBJECT(值对象)。VALUE OBJECT被实例化之后用来表示一些设计元素,对于这些设计元素,我们只关心它们是什么,而不 关心它们是谁。

在这里插入图片描述

当我们只关心一个模型元素的属性时,应把它归类为VALUE OBJECT。我们应该使这个模型 元素能够表示出其属性的意义,并为它提供相关功能。VALUE OBJECT应该是不可变的。不要为 它分配任何标识,而且不要把它设计成像ENTITY那么复杂。

关于关联:
如果说ENTITY之间的双向关联很难维护,那么两个VALUE OBJECT之间的双向关联则完 全没有意义。当一个VALUE OBJECT指向另一个VALUE OBJECT时,由于没有标识,说一个对象指向的 3 对象正是那个指向它的对象并没有任何意义的。我们充其量只能说,一个对象指向的对象与那个指 向它的对象是等同的,但这可能要求我们必须在某个地方实施这个固定规则。

Value Object 可以通过多种方式优化,比如 共享 或者 复制, 一般 Value Object 设计为不可变的

Service

定义: 有时,对象不是一个事物。
在某些情况下,最清楚、最实用的设计会包含一些特殊的操作,这些操作从概念上讲不属于 任何对象。与其把它们强制地归于哪一类,不如顺其自然地在模型中引入一种新的元素,这就是 SERVICE(服务)。

  • 一些领域概念不适合被建模为对象。如果勉强把这些重要的领域功能归为ENTITY或VALUE OBJECT的职责,那么不是歪曲了基于模型的对象的定义,就是人为地增加了一些无意义的对象

  • 好的SERVICE有以下3个特征。
    (1) 与领域概念相关的操作不是ENTITY或VALUE OBJECT的一个自然组成部分。
    (2) 接口是根据领域模型的其他元素定义的。
    (3) 操作是无状态的。

  • 当领域中的某个重要的过程或转换操作不是ENTITY或VALUE OBJECT的自然职责时,应该 在模型中添加一个作为独立接口的操作,并将其声明为SERVICE。定义接口时要使用模型语言, 并确保操作名称是UBIQUITOUS LANGUAGE中的术语。此外,应该使SERVICE成为无状态的

很多领域或应用层SERVICE是在ENTITY和VALUE OBJECT的基础上建立起来的,它们的行为类 似于将领域的一些潜在功能组织起来以执行某种任务的脚本。ENTITY和VALUE OBJECT往往由于粒度过细而无法提供对领域层功能的便捷访问。我们在这里会遇到领域层与应用层之间很微妙的分 界线。例如,如果银行应用程序可以把我们的交易进行转换并导出到一个电子表格文件中,以便 进行分析,那么这个导出操作就是应用层SERVICE。‚文件格式‛在银行领域中是没有意义的,它 也不涉及业务规则。
另一方面,账户之间的转账功能属于领域层SERVICE,因为它包含重要的业务规则(如处理 相应的借方账户和贷方账户),而且‚资金转账‛是一个有意义的银行术语。在这种情况下,SERVICE 自己并不会做太多的事情,而只是要求两个Account对象完成大部分工作。但如果将转账操作强加在Account对象上会很别扭,因为这个操作涉及两个账户和一些全局规则。

粒度 :
由于应用层负责对领域对象的行为进行协调,因此细粒度的领域对象可能会把领 域层的知识泄漏到应用层中。这产生的结果是应用层不得不处理复杂的、细致的交互,从而使得 领域知识蔓延到应用层或用户界面代码当中,而领域层会丢失这些知识。明智地引入领域层服务 有助于在应用层和领域层之间保持一条明确的界限。

Module

MODULE为人们提供了两种观察模型的方式,一是可以在MODULE中查看 细节,而不会被整个模型淹没,二是观察MODULE之间的关系,而不考虑其内部细节

MODULE之间应该是低耦合的,而在MODULE的内部则是高内聚的。耦合和内聚的 解释使得MODULE听上去像是一种技术指标,仿佛是根据关联和交互的分布情况来机械地判断它 们。然而,MODULE并不仅仅是代码的划分,而且也是概念的划分。一个人一次考虑的事情是有限 的(因此才要低耦合)。不连贯的思想和“一锅粥”似的思想同样难于理解(因此才要高内聚)。

领域对象的生命周期

在这里插入图片描述
领域对象生命周期主要挑战:
(1) 在整个生命周期中维护完整性。
(2) 防止模型陷入管理生命周期复杂性造成的困境当中。

AGGREGATE(聚合),它通过定义清晰的所属关系和边界,并避免混乱、错综复杂的对象关系网来实现模型的内聚。聚合模式对于维护生命周期各 个阶段的完整性具有至关重要的作用。
FACTORY(工厂)来创建和重建复 杂对象和AGGREGATE(聚合),从而封装它们的内部结构。最后,在生命周期的中间和末尾使用 REPOSITORY(存储库)来提供查找和检索持久化对象并封装庞大基础设施的手段。

使用AGGREGATE进行建模,并且在设计中结合使用FACTORY和REPOSITORY,这样我们就能够 在模型对象的整个生命周期中,以有意义的单元、系统地操纵它们。AGGREGATE可以划分出一个 范围,这个范围内的模型元素在生命周期各个阶段都应该维护其固定规则。FACTORY和 REPOSITORY在AGGREGATE基础上进行操作,将特定生命周期转换的复杂性封装起来。

Aggregate

减少设计中的关联有助于简化对象之间的遍历,并在某种程度上限制关系的急剧增多。但大 多数业务领域中的对象都具有十分复杂的联系,以至于最终会形成很长、很深的对象引用路径, 我们不得不在这个路径上追踪对象。在某种程度上,这种混乱状态反映了现实世界,因为现实世 界中就很少有清晰的边界。但这却是软件设计中的一个重要问题。

定义:
AGGREGATE就是一组相关对象的集合,我们把它作为数据修改的单元。每个AGGREGATE都有一个根(root)和一个边界(boundary)。边界 定义了AGGREGATE的内部都有什么。根则是AGGREGATE所包含的一个特定ENTITY。对AGGREGATE 而言,外部对象只可以引用根,而边界内部的对象之间则可以互相引用。除根以外的其他ENTITY 都有本地标识,但这些标识只在AGGREGATE内部才需要加以区别,因为外部对象除了根ENTITY之 外看不到其他对象。

例子:
汽车修配厂的软件可能会使用汽车模型。汽车是一个具有全局标识的ENTITY: 我们需要将这部汽车与世界上所有其他汽车区分开(即使是一些非常相似的汽车)。我们可以使 用车辆识别号来进行区分,车辆识别号是为每辆新汽车分配的唯一标识符。我们可能想通过4个 轮子的位臵跟踪轮胎的转动历史。我们可能想知道每个轮胎的里程数和磨损度。要想知道哪个轮 胎在哪儿,必须将轮胎标识为ENTITY。当脱离这辆车的上下文后,我们很可能就不再关心这些轮胎的标识了。如果更换了轮胎并将旧轮胎送到回收厂,那么软件将不再需要跟踪它们,它们会成 为一堆废旧轮胎中的一部分。没有人会关心它们的转动历史。更重要的是,即使轮胎被安在汽车 上,也不会有人通过系统查询特定的轮胎,然后看看这个轮胎在哪辆汽车上。人们只会在数据库 中查找汽车,然后临时查看一下这部汽车的轮胎情况。因此,汽车是AGGREGATE的根ENTITY,而 轮胎处于这个AGGREGATE的边界之内。另一方面,发动机组上面都刻有序列号,而且有时是独立 于汽车被跟踪的。在一些应用程序中,发动机可以是自己的AGGREGATE的根。

在这里插入图片描述

固定规则:
固定规则(invariant)是指在数据变化时必须保持的一致性规则,其涉及AGGREGATE成员之 间的内部关系。而任何跨越AGGREGATE的规则将不要求每时每刻都保持最新状态。通过事件处理、 批处理或其他更新机制,这些依赖会在一定的时间内得以解决。但在每个事务完成时,AGGREGATE 内部所应用的固定规则必须得到满足

  • 根ENTITY具有全局标识,它最终负责检查固定规则
  • 根ENTITY具有全局标识。边界内的ENTITY具有本地标识,这些标识只在AGGREGATE内部才是唯一的。
  • AGGREGATE外部的对象不能引用除根ENTITY之外的任何内部对象。根ENTITY可以把对内部ENTITY的引用传递给它们,但这些对象只能临时使用这些引用,而不能保持引用。根 可以把一个VALUE OBJECT的副本传递给另一个对象,而不必关心它发生什么变化,因为它只是一个VALUE,不再与AGGREGATE有任何关联。
  • 作为上一条规则的推论,只有AGGREGATE的根才能直接通过数据库查询获取。所有其他对象必须通过遍历关联来发现。
  • AGGREGATE内部的对象可以保持对其他AGGREGATE根的引用。
  • 删除操作必须一次删除AGGREGATE边界之内的所有对象。(利用垃圾收集机制,这很容易做到。由于除根以外的其他对象都没有外部引用,因此删除了根以后,其他对象均会被回收。)
  • 当提交对AGGREGATE边界内部的任何对象的修改时,整个AGGREGATE的所有固定规则都必须被满足。

在这里插入图片描述

我们应该将ENTITY和VALUE OBJECT分门别类地聚集到AGGREGATE中,并定义每个 AGGREGATE的边界。在每个AGGREGATE中,选择一个ENTITY作为根,并通过根来控制对边界内 其他对象的所有访问。只允许外部对象保持对根的引用。对内部成员的临时引用可以被传递出去, 但仅在一次操作中有效。由于根控制访问,因此不能绕过它来修改内部对象。这种设计有利于确 保AGGREGATE中的对象满足所有固定规则,也可以确保在任何状态变化时AGGREGATE作为一个 整体满足固定规则。

有一个能够声明AGGREGATE的技术框架是很有帮助的,这样就可以自动实施锁机制和其他一 些功能。如果没有这样的技术框架,团队就必须靠自我约束来使用事先商定的AGGREGATE,并按 照这些AGGREGATE来编写代码。

FACTORY

当创建一个对象或创建整个AGGREGATE时,如果创建工作很复杂,或者暴露了过多的内部结 构,则可以使用FACTORY进行封装。

对象的功能主要体现在其复杂的内部配臵以及关联方面。我们应该一直对对象进行提炼,直 到所有与其意义或在交互中的角色无关的内容被完全剔除为止。一个对象在它的生命周期中要承 担大量职责。如果再让复杂对象负责自身的创建,那么职责过载将会导致问题。

比如:

汽车发动机是一种复杂的机械装臵,它由数十个零件共同协作来履行发动机的职责——使轴 转动。我们可以试着设计一种发动机组,让它自己抓取一组活塞并塞到汽缸中,火花塞也可以自 己找到插孔并把自己拧进去。但这样组装的复杂机器可能没有我们常见的发动机那样可靠或高 效。相反,我们用其他东西来装配发动机。或许是机械师,或者是工业机器人。无论是机器人还 是人,实际上都比二者要装配的发动机复杂。装配零件的工作与使轴旋转的工作完全无关。只是 在生产汽车时才需要装配工,我们驾驶时并不需要机器人或机械师。由于汽车的装配和驾驶永远不会同时发生,因此将这两种功能合并到同一个机制中是毫无价值的。同理,装配复杂的复合对 象的工作也最好与对象要执行的工作分开。
但将职责转交给另一个相关方——应用程序中的客户(client)对象——会产生更严重的问 题。客户知道需要完成什么工作,并依靠领域对象来执行必要的计算。如果指望客户来装配它需 要的领域对象,那么它必须要了解一些对象的内部结构。为了确保所有应用于领域对象各部分关 系的固定规则得到满足,客户必须知道对象的一些规则。甚至调用构造函数也会使客户与所要构 建的对象的具体类产生耦合。结果是,对领域对象实现所做的任何修改都要求客户做出相应修改, 这使得重构变得更加困难。
当客户负责创建对象时,它会牵涉不必要的复杂性,并将其职责搞得模糊不清。这违背了领 域对象及所创建的AGGREGATE的封装要求。更严重的是,如果客户是应用层的一部分,那么职责 就会从领域层泄漏到应用层中。应用层与实现细节之间的这种耦合使得领域层抽象的大部分优势 荡然无存,而且导致后续更改的代价变得更加高昂。

对象的创建本身可以是一个主要操作,但被创建的对象并不适合承担复杂的装配操作。将这 些职责混在一起可能产生难以理解的拙劣设计。让客户直接负责创建对象又会使客户的设计陷入 混乱,并且破坏被装配对象或AGGREGATE的封装,而且导致客户与被创建对象的实现之间产生 过于紧密的耦合。

因此:
应该将创建复杂对象的实例和AGGREGATE的职责转移给单独的对象,这个对象本身可能没有 承担领域模型中的职责,但它仍是领域设计的一部分。提供一个封装所有复杂装配操作的接口, 而且这个接口不需要客户引用要被实例化的对象的具体类。在创建AGGREGATE时要把它作为一 个整体,并确保它满足固定规则。

*任何好的工厂都需满足以下两个基本需求。

(1) 每个创建方法都是原子的,而且要保证被创建对象或AGGREGATE的所有固定规则。 FACTORY生成的对象要处于一致的状态。在生成ENTITY时,这意味着创建满足所有固定规则的整 个AGGREGATE,但在创建完成后可以向聚合添加可选元素。在创建不变的VALUE OBJECT时,这意 味着所有属性必须被初始化为正确的最终状态。如果FACTORY通过其接口收到了一个创建对象的 请求,而它又无法正确地创建出这个对象,那么它应该抛出一个异常,或者采用其他机制,以确 保不会返回错误的值。

(2) FACTORY应该被抽象为所需的类型,而不是所要创建的具体类。[Gamma et al. 1995]中的 高级FACTORY模式介绍了这一话题。*

选择FACTORY及其应用位置

一般来说,FACTORY的作用是隐藏创建对象的细节,而且我们把FACTORY用在那些需要隐藏 细节的地方。这些决定通常与AGGREGATE有关。

  • 如果需要向一个已存在的AGGREGATE添加元素,可以在AGGREGATE的根上创建一个 FACTORY METHOD。这样就可以把AGGREGATE的内部实现细节隐藏起来,使任何外部客户看不到 这些细节,同时使根负责确保AGGREGATE在添加元素时的完整性
  • 另一种情况是:在一个对象上使用FACTORY METHOD,这个对象与生成另一个对象密切相关, 但它并不拥有所生成的对象。当一个对象的创建主要使用另一个对象的数据(或许还有规则)时, 则可以在后者的对象上创建一个FACTORY METHOD,这样就不必将后者的信息提取到其他地方来创建前者。这样做还有利于表达前者与后者之间的关系。
  • FACTORY与被构建对象之间是紧密耦合的,因此FACTORY应该只被关联到与被构建对象有着密 切联系的对象上。当有些细节需要隐藏(无论要隐藏的是具体实现还是构造的复杂性)而又找不 到合适的地方来隐藏它们时,必须创建一个专用的FACTORY对象或SERVICE。整个AGGREGATE通常 由一个独立的FACTORY来创建,FACTORY负责把对根的引用传递出去,并确保创建出的AGGREGATE 满足固定规则。如果AGGREGATE内部的某个对象需要一个FACTORY,而这个FACTORY又不适合在 AGGREGATE根上创建,那么应该构建一个独立的FACTORY。但仍应遵守规则——把访问限制在 AGGREGATE内部,并确保从AGGREGATE外部只能对被构建对象进行临时引用

有些情况下只需使用构造函数

在有些情况下直接使用构造函数确实是最佳选择。FACTORY实际上会使那些不具 有多态性的简单对象复杂化。

在以下情况下最好使用简单的、公共的构造函数:

  • 类(class)是一种类型(type)。它不是任何相关层次结构的一部分,而且也没有通过接口实现多态性。
  • 客户关心的是实现,可能是将其作为选择STRATEGY的一种方式。
  • 客户可以访问对象的所有属性,因此向客户公开的构造函数中没有嵌套的对象创建。
  • 构造并不复杂。
  • 公共构造函数必须遵守与FACTORY相同的规则:它必须是原子操作,而且要满足被创建对
    象的所有固定规则。

不要在构造函数中调用其他类的构造函数。构造函数应该保持绝对简单。复杂的装配,特别是AGGREGATE,需要使用FACTORY。使用FACTORY METHOD的门槛并不高。
Java类库提供了一些有趣的例子。所有集合都实现了接口,接口使得客户与具体实现之间不 产生耦合。然而,它们都是通过直接调用构造函数创建的。但是,集合类本来是可以使用FACTORY来封装集合的层次结构的。而且,客户也可以使用FACTORY的方法来请求所需的特性,然后由 FACTORY来选择适当的类来实例化。这样一来,创建集合的代码就会有更强的表达力,而且新增 集合类时不会破坏现有的Java程序。
但在某些场合下使用具体的构造函数更为合适。首先,在很多应用程序中,实现方式的选择 对性能的影响是非常敏感的,因此应用程序需要控制选择哪种实现(尽管如此,真正智能的 FACTORY仍然可以满足这些因素的要求)。不管怎样,集合类的数量并不多,因此选择并不复杂。
虽然没有使用FACTORY,但抽象集合类型仍然具有一定价值,原因就在于它们的使用模式。 集合通常都是在一个地方创建,而在其他地方使用。这意味着最终使用集合(添加、删除和检索 其内容)的客户仍可以与接口进行对话,从而不与实现发生耦合。集合类的选择通常由拥有该集
142 合的对象来决定,或是由该对象的FACTORY来决定。

接口的设计

当设计FACTORY的方法签名时,无论是独立的FACTORY还是FACTORY METHOD,都要记住以下 两点:

  • 每个操作都必须是原子的
  • Factory将与其参数发生耦合
  • 最安全的参数是那些来自较低设计层的参数。即使在同一层中,也有一种自然的分层倾向, 其中更基本的对象被更高层的对象使用
  • 另一个好的参数选择是模型中与被构建对象密切相关的对象,这样不会增加新的依赖。
  • 使用抽象类型的参数,而不是它们的具体类。FACTORY与被构建对象的具体类发生耦合,而无需与具体的参数发生耦合。

固定规则的相关逻辑应放置在哪里

FACTORY负责确保它所创建的对象或AGGREGATE满足所有固定规则,然而在把应用于一个对象的规则移到该对象外部之前应三思。FACTORY可以将固定规则的检查工作委派给被创建对象, 而且这通常是最佳选择。

虽然原则上在每个操作结束时都应该应用固定规则,但通常对象所允许的转换可能永远也不 会用到这些规则。可能ENTITY标识属性的赋值需要满足一条固定规则。但该标识在创建后可能一 直保持不变。VALUE OBJECT则是完全不变的。如果逻辑在对象的有效生命周期内永远也不被用到, 那么对象就没有必要携带这个逻辑。 在这种情况下,FACTORY是放臵固定规则的合适地方,这样 可以使FACTORY创建出的对象更简单。

ENTITY FACTORY与VALUE OBJECT FACTORY

由于VALUE OBJECT是不可变 的,因此,FACTORY所生成的对象就是最终形式。因此FACTORY操作必须得到被创建对象的完整 描述。而ENTITY FACTORY则只需具有构造有效AGGREGATE所需的那些属性。对于固定规则不关心 的细节,可以之后再添加。

我们来看一下为ENTITY分配标识时将涉及的问题(VALUE OBJECT不会涉及这些问题)。正如 第5章所指出的那样,既可以由程序自动分配一个标识符,也可以通过外部(通常是用户)提供 一个标识符。如果客户的标识是通过电话号码跟踪的,那么该电话号码必须作为参数被显式地传 递给FACTORY。当由程序分配标识符时,FACTORY是控制它的理想场所。尽管唯一跟踪ID实际上 144 是由数据库“序列”或其他基础设施机制生成的,但FACTORY知道需要什么样的标识,以及将标 识放到何处。

重建已存储的对象

用于重建对象的FACTORY与用于创建对象的FACTORY很类似,主要有以下两点不同:

  1. 用于重建对象的ENTITY FACTORY不分配新的跟踪ID。如果重新分配ID,将丢失与先前对 象的连续性。因此,在重建对象的FACTORY中,标识属性必须是输入参数的一部分。
  2. 当固定规则未被满足时,重建对象的FACTORY采用不同的方式进行处理:
    当创建新对象时,如果未满足固定规则,FACTORY应该简单地拒绝创建对象,但在重建对象时则需要更灵活的响应。如果对象已经在系统的某个地方存在(如在数据库中),那么不能忽略这个事实。但是, 同样也不能任凭规则被破坏。必须通过某种策略来修复这种不一致的情况,这使得重建对象比创 建新对象更困难。

REPOSITORY

背景: 我们可以通过对象之间的关联来找到对象。但当它处于生命周期的中间时,必须要有一个起 点,以便从这个起点遍历到一个ENTITY或VALUE。
无论要用对象执行什么操作,都需要保持一个对它的引用。那么如何获得这个引用呢?一种 方法是创建对象,因为创建操作将返回对新对象的引用。第二种方法是遍历关联。我们以一个已 知对象作为起点,并向它请求一个关联的对象。这样的操作在任何面向对象的程序中都会大量用 到,而且对象之间的这些链接使对象模型具有更强的表达能力。但我们必须首先获得作为起点的 那个对象。

客户需要一种有效的方式来获取对已存在的领域对象的引用。如果基础设施提供了这方面的 便利,那么开发人员可能会增加很多可遍历的关联,这会使模型变得非常混乱。另一方面,开发 人员可能使用查询从数据库中提取他们所需的数据,或是直接提取具体的对象,而不是通过 AGGREGATE的根来得到这些对象。这样就导致领域逻辑进入查询和客户代码中,而ENTITY和 VALUE OBJECT则变成单纯的数据容器。采用大多数处理数据库访问的技术复杂性很快就会使客 户代码变得混乱,这将导致开发人员简化领域层,最终使模型变得无关紧要。**

目标: 根据到目前为止所讨论的设计原则,如果我们找到一种访问方法,它能够明确地将模型作为 焦点,从而应用这些原则,那么我们就可以在某种程度上缩小对象访问问题的范围

初学者可 以不必关心临时对象。临时对象(通常是VALUE OBJECT)只存在很短的时间,在客户操作中用到 它们时才创建它们,用完就删除了。我们也不需要对那些很容易通过遍历来找到的持久对象进行 查询访问。例如,地址可以通过Person对象获取。而且最重要的是,除了通过根来遍历查找对象 这种方法以外,禁止用其他方法对AGGREGATE内部的任何对象进行访问。
持久化的VALUE OBJECT一般可以通过遍历某个ENTITY来找到,在这里ENTITY就是把对象封
装在一起的AGGREGATE的根。事实上,对VALUE的全局搜索访问常常是没有意义的,因为通过属 性找到VALUE OBJECT相当于用这些属性创建一个新实例。但也有例外情况。例如,当我在线规划 旅行线路时,有时会先保存几个中意的行程,过后再回头从中选择一个来预订。这些行程就是 VALUE(如果两个行程由相同的航班构成,那么我不会关心哪个是哪个),但它们已经与我的用 户名关联到一起了,而且可以原封不动地将它们检索出来。另一个例子是“枚举”,在枚举中一 个类型有一组严格限定的、预定义的可能值。但是,对VALUE OBJECT的全局访问比对ENTITY的全 局访问更少见,如果确实需要在数据库中搜索一个已存在的VALUE,那么值得考虑一下,搜索结 果可能实际上是一个ENTITY,只是尚未识别它的标识。

现在可以更精确地将问题重新表述如下:
_在所有持久化对象中,有一小部分必须通过基于对象属性的搜索来全局访问。当很难通过遍 历方式来访问某些AGGREGATE根的时候,就需要使用这种访问方式。它们通常是ENTITY,有时 是具有复杂内部结构的VALUE OBJECT,还可能是枚举VALUE。而其他对象则不宜使用这种访问方 式,因为这会混淆它们之间的重要区别。随意的数据库查询会破坏领域对象的封装和 AGGREGATE。技术基础设施和数据库访问机制的暴露会增加客户的复杂度,并妨碍模型驱动的 设计。

有得必有失,我们应该注意失去了什么。我们已经不再考虑领域模型中的概念。代码也不再 表达业务,而是对数据库检索技术进行操纵。REPOSITORY是一个简单的概念框架,它可用来封装 这些解决方案,并将我们的注意力重新拉回到模型上。_

因此:
为每种需要全局访问的对象类型创建一个对象,这个对象相当于该类型的所有对象在内存中 的一个集合的“替身”。通过一个众所周知的全局接口来提供访问。提供添加和删除对象的方法,
151 用这些方法来封装在数据存储中实际插入或删除数据的操作。提供根据具体条件来挑选对象的方 法,并返回属性值满足查询条件的对象或对象集合(所返回的对象是完全实例化的),从而将实 际的存储和查询技术封装起来。只为那些确实需要直接访问的AGGREGATE根提供REPOSITORY。 让客户始终聚焦于模型,而将所有对象的存储和访问操作交给REPOSITORY来完成。

REPOSITORY有很多优点,包括:

  • 它们为客户提供了一个简单的模型,可用来获取持久化对象并管理它们的生命周期;
  • 它们使应用程序和领域设计与持久化技术(多种数据库策略甚至是多个数据源)解耦;
  • 它们体现了有关对象访问的设计决策;
  • 可以很容易将它们替换为“哑实现”(dummy implementation),以便在测试中使用(通常
    使用内存中的集合)。

实现:

  • 对类型进行抽象。REPOSITORY“含有”特定类型的所有实例,但这并不意味着每个类都 需要有一个REPOSITORY。类型可以是一个层次结构中的抽象超类(例如,TradeOrder可以 是BuyOrder或SellOrder)。类型可以是一个接口——接口的实现者并没有层次结构上的关 联,也可以是一个具体类。记住,由于数据库技术缺乏这样的多态性质,因此我们将面 临很多约束。
  • 充分利用与客户解耦的优点。 我们可以很容易地更改REPOSITORY的实现,但如果客户直接调用底层机制,我们就很难修改其实现。也可以利用解耦来优化性能,因为这样就可以使用不同的查询技术,或在内存中缓存对象,可以随时自由地切换持久化策略。通过 提供一个易于操纵的、内存中的(in-memory)哑实现,还能够方便客户代码和领域对象
    的测试。
  • 将事务的控制权留给客户。 尽管REPOSITORY会执行数据库的插入和删除操作,但它通常不会提交事务。例如,保存数据后紧接着就提交似乎是很自然的事情,但想必只有客户 才有上下文,从而能够正确地初始化和提交工作单元。如果REPOSITORY不插手事务控制, 那么事务管理就会简单得多。

REPOSITORY与FACTORY的关系

从领域驱动设计的角度来看,FACTORY 和REPOSITORY具有完全不同的职责。FACTORY负责制造新对象,而REPOSITORY负责查找已有对象。 REPOSITORY应该让客户感觉到那些对象就好像驻留在内存中一样。对象可能必须被重建(的确, 可能会创建一个新实例),但它是同一个概念对象,仍旧处于生命周期的中间。

REPOSITORY也可以委托FACTORY来创建一个对象,这种方法(虽然实际很少这样做,但在理 论上是可行的)可用于从头开始创建对象,此时就没有必要区分这两种看问题的角度了
在这里插入图片描述
这种职责上的明确区分还有助于FACTORY摆脱所有持久化职责。FACTORY的工作是用数据来 实例化一个可能很复杂的对象。如果产品是一个新对象,那么客户将知道在创建完成之后应该把 它添加到REPOSITORY中,由REPOSITORY来封装对象在数据库中的存储:
在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值