五 领域驱动设计-软件中所表示的模型


目录
  • 软件中所表示的模型
    • 关联
    • ENTITY(又称为REFERENCE OBJECT)
    • 模式:VALUE OBJECT
    • SERVICE
    • MODULE(也称为PACKAGE)

软件中所表示的模型

表示模型的3种模型元素模式:ENTITY、VALUE OBJECT和SERVICE。从表面上看,定义那些用来捕获领域概念的对象很容易,但要想反映其含义却很困难。这要求我们明确区分各种模型元素的含义,并与一系列设计实践结合起来,从而开发出特定类型的对象。

个人理解:就是实体类(entity),VALUE OBJECT(值对象,我没懂)和服务(service),开发人员一看就懂了。

关联

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

一个显示了顾客与销售代表之间关联的模型有两个含义。一方面,它把开发人员所认为的两个真实的人之间的关系抽象出来。另一方面,它相当于两个Java对象之间的对象指针,或者相当于数据库查询(或类似实现)的一种封装。

例如,一对多关联可以用一个集合类型的实例变量来实现。但设计无需如此直接。可能没有集合,这时可以使用一个访问方法(accessor method)来查询数据库,找到相应的记录,并用这些记录来实例化对象。这两种设计方法反映了同一个模型。设计必须指定一种具体的遍历机制,这种遍历的行为应该与模型中的关联一致。

现实生活中有大量?多对多?关联,其中有很多关联天生就是双向的。我们在模型开发的早期进行头脑风暴活动并探索领域时,也会得到很多这样的关联。但这些普遍的关联会使实现和维护变得很复杂。此外,它们也很少能表示出关系的本质。

至少有3种方法可以使得关联更易于控制。

(1) 规定一个遍历方向。

(2) 添加一个限定符,以便有效地减少多重关联。

(3) 消除不必要的关联。

尽可能地对关系进行约束是非常重要的。双向关联意味着只有将这两个对象放在一起考虑才能理解它们。当应用程序不要求双向遍历时,可以指定一个遍历方向,以便减少相互依赖,并简化设计。理解了领域之后就可以自然地确定一个方向。

个人理解:上面的内容技术人员不要看啦,很难理解,我告诉你是怎么一回事。技术角度就是两个对象的关联关系如下:一对一(一个人一个鼻子),一对多(一个人两个手臂),多对多(一个人报名多个课程,一个课程有多个人报名)。是不是很像数据库设计模型?

ENTITY(又称为REFERENCE OBJECT)

开始讲故事:可以不听,因为我会用一句话总结,非常简单。

软件系统中的大多数?ENTITY?并不是人,也不是其通常意义上所指的?实体?或?存在?。ENTITY可以是任何事物,只要满足两个条件即可,一是它在整个生命周期中具有连续性,二是它的区别并不是由那些对用户非常重要的属性决定的。ENTITY可以是一个人、一座城市、一辆汽车、一张彩票或一次银行交易。

一位女房东起诉了我,要求我赔偿她房屋的大部分损失。诉状上是这样写的:房间的墙上有很多小洞,地毯上满是污渍,水池里的脏物散发出的腐蚀性气体导致厨房墙皮脱落。法庭文件认定我作为承租人应该为这些损失负责,依据就是我的名字和我当时的地址。这把我完全搞糊涂了,因为我从未去过那个被损坏的房子。

过了一会儿,我意识到这一定是认错人了。我给原告打电话,告诉她这一点,但她并不相信我。几个月以来,上一位租客一直在躲避她。如何才能证明我不是那个破坏她房屋的人呢?现在电话簿里只有一个Eric Evans名字,那就是我。还是电话簿成了我的救星。由于我在这所公寓里已经住了两年,于是我问她是否还有去年的电话簿。她找到了电话簿,发现有与我同名的人(我就在那个人下面),她意识到我不是她要起诉的那个人,于是向我道歉,并答应撤销起诉。

计算机可不会这么?足智多谋?。软件系统中的错误标识将导致数据破坏和程序错误。

主要由标识定义的对象被称作ENTITY[。ENTITY(实体)有特殊的建模和设计思路。它们具有生命周期,这期间它们的形式和内容可能发生根本改变,但必须保持一种内在的连续性。为了有效地跟踪这些对象,必须定义它们的标识。它们的类定义、职责、属性和关联必须由其标识来决定,而不依赖于其所具有的属性。即使对于那些不发生根本变化或者生命周期不太复杂的ENTITY,也应该在语义上把它们作为ENTITY来对待,这样可以得到更清晰的模型和更健壮的实现。

当然,软件系统中的大多数?ENTITY?并不是人,也不是其通常意义上所指的?实体?或?存在?。ENTITY可以是任何事物,只要满足两个条件即可,一是它在整个生命周期中具有连续性,二是它的区别并不是由那些对用户非常重要的属性决定的。ENTITY可以是一个人、一座城市、一辆汽车、一张彩票或一次银行交易。

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

在现实世界中,并不是每一个事物都必须有一个标识,标识重不重要,完全取决于它是否有用。实际上,现实世界中的同一个事物在领域模型中可能需要表示为ENTITY,也可能不需要表示为ENTITY。

个人理解:一句话,设计实体的时候最好有一个全局唯一id的属性。相信做技术的会觉得,上面说了这么多就是这个意思,太简单了,没错,就是这么简单,关键是,我们有本事写那么多的字来表述(瞎扯)吗?

模式:VALUE OBJECT

很多对象没有概念上的标识,它们描述了一个事务的某种特征。当一个小孩画画的时候,他注意的是画笔的颜色和笔尖的粗细。但如果有两只颜色和粗细相同的画笔,他可能不会在意使用哪一支。如果有一支笔弄丢了,他可以从一套新笔中拿出一支同样颜色的笔来继续画,根本不会在意已经换了一支笔。

问问孩子冰箱上的画都是谁画的,他会很快辨认出哪些是他画的,哪些是他姐姐画的。姐弟俩有一些实用的标识来区分自己,与此类似,他们完成的作品也有。但设想一下,如果孩子必须记住哪些线条是用哪支笔画的,情况该有多么复杂?如果这样的话,画画将不再是小孩子的游戏了。

由于模型中最引人注意的对象往往是ENTITY,而且跟踪每个ENTITY的标识是极为重要的,因此我们很自然地会想到为每个领域对象都分配一个标识。实际上,一些框架确实为每个对象分配了一个唯一的ID。

这样一来,系统就必须处理所有这些ID的跟踪问题,从而导致许多本来可能的性能优化不得不被放弃。此外,人们还需要付出大量的分析工作来定义有意义的标识,还需要开发出一些可靠的跟踪方式,以便在分布式系统或在数据库存储中跟踪对象。同样重要的是,盲目添加无实际意义的标识可能会产生误导。它会使模型变得混乱,并使所有对象看起来千篇一律。

个人理解:VALUE OBJECT 可以理解entity去掉唯一性的id,变成了无状态的一个对象。

VALUE OBJECT可以是其他对象的集合。在房屋设计软件中,可以为每种窗户样式创建一个对象。我们可以将?窗户样式?连同它的高度、宽度以及修改和组合这些属性的规则一起放到?窗户?对象中。这些窗户就是由其他VALUE OBJECT组成的复杂VALUE OBJECT。它们进而又被合并到更大的设计元素中,如?墙对象。

VALUE OBJECT经常作为参数在对象之间传递消息。它们常常是临时对象,在一次操作中被创建,然后丢弃。VALUE OBJECT可以用作ENTITY(以及其他VALUE)的属性。我们可以把一个人建模为一个具有标识的ENTITY,但这个人的名字是一个VALUE。

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

SERVICE

有时,对象不是一个事物。在某些情况下,最清楚、最实用的设计会包含一些特殊的操作,这些操作从概念上讲不属于任何对象。SERVICE是作为接口提供的一种操作,它在模型中是独立的,它不像ENTITY和VALUE OBJECT那样具有封装的状态。SERVICE是技术框架中的一种常见模式,但它们也可以在领域层中使用。所谓SERVICE,它强调的是与其他对象的关系。与ENTITY和VALUE OBJECT不同,它只是定义了能够为客户做什么。SERVICE往往是以一个活动来命名,而不是以一个ENTITY来命名,也就是说,它是动词而不是名词。SERVICE也可以有抽象而有意义的定义,只是它使用了一种与对象不同的定义风格。SERVICE也应该有定义的职责,而且这种职责以及履行它的接口也应该作为领域模型的一部分来加以定义。操作名称应来自于UBIQUITOUS LANGUAGE,如果UBIQUITOUS LANGUAGE中没有这个名称,则应该将其引入到UBIQUITOUS LANGUAGE中。参数和结果应该是领域对象。

好的SERVICE有以下3个特征。

(1) 与领域概念相关的操作不是ENTITY或VALUE OBJECT的一个自然组成部分。

(2) 接口是根据领域模型的其他元素定义的。

(3) 操作是无状态的。

个人理解:就是面向接口的设计,提供的就是服务。

MODULE(也称为PACKAGE)

MODULE是一个传统的、较成熟的设计元素。每个人都会使用MODULE,但却很少有人把它们当做模型中的一个成熟的组成部分。代码按照各种各样的类别进行分解,有时是按照技术架构来分割的,有时是按照开发人员的任务分工来分割的。甚至那些从事大量重构工作的开发人员也倾向于使用项目早期形成的一些MODULE。MODULE之间应该是低耦合的,而在MODULE的内部则是高内聚的。耦合和内聚的解释使得MODULE听上去像是一种技术指标,仿佛是根据关联和交互的分布情况来机械地判断它们。然而,MODULE并不仅仅是代码的划分,而且也是概念的划分。一个人一次考虑的事情是有限的(因此才要低耦合)。不连贯的思想和“一锅粥”似的思想同样难于理解(因此才要高内聚)。

低耦合高内聚作为通用的设计原则既适用于各种对象,也适用于MODULE,但MODULE作为一种更粗粒度的建模和设计元素,采用低耦合高内聚原则显得更为重要。这些术语由来已久,早在[Larman 1998]中就从模式角度对其进行了解释。

个人理解:包,关注高内聚低耦合就对了。