20 值对象(下):值对象和实体的本质区别是什么?
你好,我是钟敬。
在前两节课,我们学习了值对象的基本概念、编程实现以及值对象的优点,基本上已经可以开始在实践中应用了。现在我们已经知道,实体是靠独立于其他属性的标识来确定同一性的,而值对象以本身的值来确定同一性,没有独立于其他属性的标识;理论上,实体是可变的,而值对象是不可变的。
但是,还有两个问题没有彻底解决:一个问题是怎么在领域模型里表示值对象;另一个问题更深一点,那就是,实体和值对象有这种区别的根源是什么?如果不理解这一点,在遇到一些疑难问题的时候,仍然无法分辨。
这节课,我们就来解决这两个问题。开始之前,我先提示一下,后面有不少图片和代码,你可以边看文稿边听我说。
UML的两个知识点
为了讲清楚怎么在模型里表示值对象,我们先要讨论UML里的两个知识点。
属性和关联的等价性
第一个知识点是“属性(attribute)和关联(association)的等价性”。这在UML里是一个比较深入的原理。
什么意思呢?咱们先来看看后面这个简单的模型图。
这个图的意思是,员工作为组织成员的时候,组织和员工之间是一对多的关联。其实这种关联还有另一种画法,虽然形式不同,但意思是一样的。可以看看下面这张图。
看完以后,你是不是觉得有点奇怪。表示关联的线跑哪儿去了?
首先我们仔细看看员工这个类型。里面的属性写作“直属组织: 组织[1..1]”,冒号前面的直属组织是属性的名称,冒号后面的组织是这个属性的类型,也就是上面画出来的那个方框所代表的组织类型,我把这个对应关系用蓝色的字标出来了。
类型名后面的中括号就是这个属性的“多重性”,这里的[1..1]就代表员工至少属于一个直属组织,最多也只属于一个直属组织。你应该发现了,这个直属组织属性,其实就代表了原来图里的那根“线”。从组织类型那边看,道理也是一样的。
下面我们把这两个图并列起来,方便你进一步理解它们的对应关系。
这个图的左边是按照“关联”的方式画的,而右边是按照“属性”的方式画的。红色的线表示等价关系。
我们来梳理一下。
第一,左边员工充当的成员角色,等价于右边组织类型里的成员属性。
第二,左边的员工类型,等价于右边成员属性的员工类型。
第三,左边的多重性 “0..*”,等价于右边成员属性的员工类型后面的 “[0..*]”。
另外,右边的组织类型的成员属性,或者员工类型的直属组织属性,都隐含着左图里的关联线,但没有画出来。
在上面的图里,左图用一条表示关联的线来表达组织和员工之间的关系,右图则用属性的方式来表达,两者的含义是等价的,这就是关联和属性的等价性。
顺便说一个建模初学者常见的错误,我们看看下面这张图。
发现问题在哪了吗?直属组织这个属性本身,就意味着组织和员工之间隐含着关联线。因此两者表达的意思发生了重复,而一张图里不应该有这样的重复。所以,要么保留属性,删掉关联,要么保留关联,删掉属性,但两者不能同时出现。
属性要素的省略
讲完了属性和关联的等价性,我们再说UML的第二个知识点——属性要素的省略。
UML里的很多元素都是可以省略的,关键看你想强调什么。总的原则是,在表意清楚的前提下,尽量简洁。为了讲值对象的建模,我们今天主要讨论和属性有关的要素的省略。
还是拿员工类型来举例吧,我们看看它的直属组织属性。
之前我们建模的时候好像没写这么复杂,一般只写了属性名,这是因为其他部分被省略了。
在这个例子里,如果我们觉得“一个员工肯定只属于一个组织,不可能属于两个”这一点很显然,根本不必强调的话,那么就可以省掉多重性,变成下面的样子。
如果我们还觉得直属组织的类型显然就是组织,没必要强调,那么可以再省去类型的说明。像下面这样。
反之,如果我们更想强调,员工有一个类型为组织的属性,至于这个属性叫什么名字反而不重要,那么就可以省掉属性名,只保留类型,变成下面这样。
如果我们觉得这个属性根本不重要,即使省去也不会影响我们理解整个业务知识,那么可以省掉整个属性的描述,画成下图里的样子。
现在,你已经理解了属性要素的省略方法了吧。总之,核心思想就是,属性的作用是表达领域知识,我们应该省略掉显而易见或者不关注的内容,标识出需要强调的部分。
如何在模型里表示值对象
好了,我们再回过头,思考一下关联和属性的等价性。你是不是已经在思考一个问题:既然在画领域模型的时候可以采用两种方式,那么我们该怎么选择呢?下面我们通过分析值对象在模型里的表示方式,把这个问题讲清楚。
值对象的基本表示方法
在第18课,我们重点讲过两个值对象,一个是时间段,一个是员工状态,我们还是沿用这两个例子。
首先聊聊时间段应该怎么在模型里表示。先看看我们之前画的时间段类型。
我们首先给这个类加一个衍型,来说明这是值对象。
《DDD》原书里并没有给出表示值对象的衍型。用<<value>>这个衍型来表示值对象,是 Martin Fowler的习惯,我们沿用了这个做法。那么,需不需要用<<entity>> 衍型来标识实体呢?
这样做当然也没错,但是一般来说没必要。这是因为除了值对象,就是实体,所以我们把值对象标识出来,就足以区分两者了。
工作经验这个实体有一个时间段属性,原来我们画成了下面这样。
这种画法是没有问题的。这里用了属性而不是关联的方式,来表达工作经验和时间段的关系,并且在属性后面省略了时间段的类型。
如果把整个属性写全,就是后面这样。
这里的属性名和类型名恰好同名,但表达的是不同的概念,要注意区分。
那么问题又来了,请你想一下,这里可不可以用关联的形式来表达呢?如果用关联的形式,就会是下面的样子。
注意,时间段和工作经验是一对多关联。也就是说,一个工作经验只有一个时间段,而一个时间段,可以用于很多人的工作经验。
另外你可能已经注意到,时间段里用到的日期其实也是值对象,如果再展开一级的话,就成了下面这样。
这种画法虽然含义是对的,但你是不是在直觉上已经觉得,没有必要做这样的展开呢?如果我们把一张图上的所有值对象都这样展开,这张图就会变得非常复杂,重点也不突出,那就很难理解了。
一般而言,我们在领域知识里主要考虑的还是实体和实体之间的关系。而值对象多数是用来说明实体的属性的,没有必要关注一个值对象和使用它的实体之间到底是一对一还是一对多。所以我的建议是,一般情况下,实体之间的关系用关联来表达,而实体和值对象之间的关系用属性来表达。
在实践的时候,我常常发现有些小伙伴不太容易理解值对象和实体之间的一对多关系。尽管在实际和业务人员建模的时候,我们一般不需要体现出这种关系,但是从学习的角度,我们自己心里还是应该充分理解的。所以,在自己练习的时候,我倒是建议你试试把值对象展开成关联的方式,比较一下它和属性方式的区别。这是因为用了关联方式以后,这种一对多的关系才能充分展现出来。
枚举型值对象
接下来,我们看另一个值对象——员工状态。按照前面的方法,我们可以把员工和员工状态画成下面的样子。
回忆一下我们在第18课里讲过的值对象分类,可以发现员工状态和时间段有两个区别。第一,员工状态是预定义的,时间段是非预定义的;第二,员工状态是依附于实体的,而时间段是独立的。
我们先说说第一点,员工状态是预定义的。上图里的注释,就说明了预定义的三种员工状态。这种属性,也称为可枚举的。在UML里有一个表示枚举类型的衍型<<enumeration>>,用了这个衍型以后,员工状态就可以像下面这样表示。
对于枚举类型,符号的下面那栏里就不是属性了,而是可供选择的枚举值。
值对象放在哪个包
我们再来看第二点区别:员工状态是依附于实体的,而时间段是独立的。这个差别,影响的是模块或者聚合包的划分。明白了这一点,我们就可以画出下面这张图。
由于员工状态是依附于员工实体的,所以放在表示员工聚合的包里是合适的。时间段虽然被工作经验使用,但是并不局限于工作经验。相反,它是公共的,可以被很多不同的实体使用,所以放在一个公共包里。
值对象的省略
还有一种情况,如果我们不是刻意要强调员工状态这个类型,也可以干脆省略掉不画,并且可以把枚举值的选项直接写在实体的属性后面。像下面这样。
员工状态属性后面的大括号,我们之前学过,表示“约束”。只不过原来的约束是写在注解里,现在直接附在属性后面。竖杠(|)表示“或”的关系。也就是说,我们对员工状态做了约束,它只能是试用期、正式工、终止这三种状态中的一种。这种方式会更简洁一些。
值对象和实体的本质区别
好,现在我们终于可以来回答课程开头提的那个问题了:实体和值对象之间的区别的本质和根源到底是什么?
之所以这个问题有点难,是因为,我们必须从人类认识事物的方式这个角度,才能把这个问题说清楚。也就是说,有点上升到哲学层面了。
实体一般是我们能够感受到的客观存在的外部事物。比如说一张桌子,人看到,是一张桌子。狗看到,也是一张桌子,虽然狗不知道这个东西叫“桌子”。从这个角度来说,对实体(至少是自然界里的实体)的直觉认识能力,是人和动物共有的。
也有一些实体是人想象出来的,例如仙女。尽管仙女很可能不存在,但她是以实际存在的事物为蓝本想象出来的,在人心中的感受是类似的。还有一些自然界不存在的事物,比如“公司”、“合同”等等。这些是人类社会关系的产物,但它们在人头脑中的感受也与自然实体类似。
值对象就不一样了。我们从5张桌子、5只山羊、5棵树这些事物里,抽象出“5”这个概念,用于描述数量。“5”是整数,整数是值对象的类型,“5”是值对象的实例。
我们又发明了字母,并约定用字符串“table”来指称“桌子”这个概念。“字符串”是值对象的类型,“table”是字符串类型的一个实例。
所以,值对象是人类为了认识和描述事物的属性,在头脑里经过抽象思维创造出来的概念。这些概念在自然界中是不存在的。只有智慧生物才有这种抽象能力。狗的头脑里是不会有“5”和“table”的概念的。
这种认识论上的区别也得到了科学的佐证。生物学家巴浦洛夫提出了“第一信号系统”和“第二信号系统”学说。这个学说认为,大脑皮质最基本的活动是信号活动。信号可以分成两类:一类是现实的、具体的刺激,比如声、光、热等等,称为第一信号;另一类是抽象的刺激,比如说语言和数字,称为第二信号。
对第一信号发生反应的大脑机能,叫做第一信号系统,是动物和人共有的。对第二信号发生反应的机能,叫作第二信号系统,是人类所特有的。联系到DDD,大体上可以说,对实体的认识主要是通过第一信号系统,而对值对象的认识则是通过第二信号系统。
现实中的事物,也就是实体,总有一个产生和消亡的过程,在这个过程里,各种属性也可能发生变化,因此是可变的。
而值对象则是纯粹的概念产物,唯一的目的就是方便人的思考和沟通。所以,这样的概念本身并没有自然的产生和消亡过程,也不需要改变。5 就是 5 , 5 如果变成 6 ,那么就已经是另一个值对象了,原来的 5 还在那里,并没有改变。换句话说,值对象并没有实体意义上的“生命周期”。因此,谈论值对象的改变,本身是没有意义的,这就是值对象不变性的本质原因。
总结
好,这节课的主要内容就讲完了,下面来总结一下。
在UML里,属性和关联具有等价性。也就是说,两个领域对象之间的关系,既可以用关联的方式表达,也可以用属性的方式来表达。两种方式从涵义上是等价的,但是对于同一个关系,不能同时用两种方式。
而在实践中,由于我们往往更关心的是实体之间的关联关系,所以一般建议,在领域模型图里,实体之间的关系用关联来表达,而实体和值对象之间的关系用属性来表达。不过,在自己练习的时候,用关联的方式画一画值对象,有助于你的深入理解。
值对象本身,可以用<>衍型来标识。对于可枚举的值对象,还可以采用UML专门的枚举衍型来表达。对于依附于实体的值对象,可以放在实体所属的聚合包里,而不依赖于实体的值对象,可放在公共包里。如果某些值对象不需要强调,那就不必专门画出来。
实体和值对象的本质区别在于,实体是人通过感官可以感觉到的客观存在的事物,或者以存在的事物为蓝本想象出来的事物;而值对象是为了描述事物,由人抽象出来的纯粹概念。讨论值对象的变化是没有意义的。
思考题
我给你留了两道思考题。
1.有人认为值对象就是DTO(数据传输对象),你同意吗,为什么?
2.你觉得和业务人员谈需求的时候,需要强调值对象这个概念吗?
好,今天的课程结束了,有什么问题欢迎在评论区留言。下节课,我们会讲一种能丰富领域知识,并且化简关联关系的建模技术——限定。
- 神经蛙 👍(9) 💬(5)
牛逼这钱值 比另外一门ddd好多了
2023-03-03 - 赵晏龙 👍(8) 💬(1)
1.值对象还在Domain范围,DTO属于应用范围,风马牛不相及。 2.我觉得有必要,但问的方式应该更加通俗,比如说,A1 A2中有个B属性,A1的B变了A2是否应该跟着变?至于理解是否值对象,实际应用中业务方的学习成本会较高。
2023-02-08 - 酱油丹 👍(4) 💬(2)
老师,请教一个问题,按这节课的讲述,工作经验其实也可以认为是值对象?理由如下: 1. 无单独的标识,其所有的属性集合就是自身的标识 2. 也是一个抽象的概念,并不是一个客观存在的事物
2023-03-29 - Jxin 👍(4) 💬(2)
内容: 1.巧了,我画领域模型图也会有值对象的缩写。不过我的应用场景是分阶段的,早期可研阶段领域模型的重心是与产品沟通对齐,所以以突出核心模型为主,把其他分支全部隐在属性里。模型的意义是突出重心隐藏复杂。都后面软件建设,要重新划,这个时候是承接上层知识和约束并指导后续软件实现。 2.对于值对象和实体的描述,说得很对。我没在这种维度和团队成员探讨过,如果有实践经验,希望能补充下。我觉得对这块的描述很多说法都对,但关键的还是得看有没有用。这个说法能方便我团队间知识传递吗?这个说法能更好的指导编码实现吗?如果不行,那就只是对。 课后题: 1.反过来可以说是,从本章的内容来看。但没有用,区分dto是否值对象对我实现业务逻辑无帮助。因为如果我的业务逻辑直接依赖dto那本身就是种坏味道。而不依赖,只是传输数据的载体,那管他是啥呢,无所谓。 2.可以,但不建议。能聊沟通信息的厚度能有很大提升,对长期协作有帮助。但对人员的要求太高了,都是流水的兵,这种要求其实也是对特定人的一种高耦合,管理角度看这是一种不稳定。更何况还有干外包的,打一枪放一炮的,还能要求客户水平得如何?
2023-01-28 - 末日,成欢 👍(2) 💬(2)
老师,读书有一点困惑的地方 我之前对实体的理解是存在唯一标识,并且唯一标识在整个生命周期内不可变,实体的其他属性对于实体来说不重要,是可变的 但是书里说的是ENTITY可以是任何事物,只要满足两个条件即可, 一是它在整个生命周期中具有连续性,这个连续性想要说什么? 书里说的这句话没有前后文,没理解。 还有5.2.1ENTITY最基本的职责是确保连续性,以便使其行为更清楚且可预测 这里的连续性说明的是什么? 麻烦老师解惑下
2023-09-16 - 龙腾 👍(2) 💬(1)
1、DTO好像没有严格定义里面有哪些数据吧,可能是实体也可以是一些零碎的属性,或者是实体和一些其他属性的组合。 2、我觉得一般不用刻意强调,对于实体和值对象的基本概念本来就存在于每个人的大脑中。在构建技术模型和实现时可以根据需要加以区分。
2023-01-22 - tt 👍(2) 💬(1)
我觉得值对象不完全是DTO,因为DTO也可以对应客观存在的事物,或者以存在的事物为蓝本想象出来的事物,只是它不需要有逻辑,所以只剩下了属性。
2023-01-19 - plimlips 👍(1) 💬(2)
1.DTO不是值对象,他是技术对象,是领域模型和计算机环境的映射 2.分类一般不用强调,但要把这两种对象都分析出来,分类是有助于领域模型落地的
2023-01-25 - aoe 👍(1) 💬(1)
第一个问题本来觉得是一样的,大家都可以是不可变的数据,这不一样吗?但看了老师的答案后才明白: 值对象要处理领域逻辑,DTO 只是数据传输 看来实体、值对象的共同点是:都可以处理领域逻辑
2023-01-25 - Geek4329 👍(0) 💬(1)
这课程质量太高了,感谢分享
2024-10-16 - J.Smile 👍(0) 💬(2)
最近对值对象是否必须依附于实体存在,用来描述实体产生了疑惑。经过谷歌,发现也是众说纷纭。 个人比较倾向于不依附实体,虽然可以描述实体。 比如经常从另外一个限界上下文获取的临时业务数据,参与业务计算后就不再使用了。不需要存储,也不需要放到聚合根,非根实体也不需要存。还有个例子就是原著中的SPECIFICATION模式
2024-07-04 - 神经蛙 👍(0) 💬(1)
只对象度量了实体的某个属性,把不同相关的属性组成了一个概念整体
2023-03-03 - py 👍(0) 💬(1)
1. 不是一个东西 2.需要和业务人员讨论,这个其实是提炼抽象的一些业务知识
2023-02-24 - Y024 👍(1) 💬(0)
属性省略的原则,应该和编写代码的原则一样,面向读者而非面向作者。 不应该有太多的隐性知识要求。
2023-01-31 - 苏籍 👍(0) 💬(0)
关于值对象 在落地存储时候的 实现 好像没有找到,因为实体里面有是否变更这样的枚举,可以根据枚举进行相应数据库操作 值对象的数据库操作策略是什么? 是值对象在本次请求中存在就替换,不存在就删除吗,还是用什么方法
2023-07-04