DDD领域驱动设计批评文集>>
《软件方法》强化自测题集>>
《软件方法》各章合集>>
8.3.3.4 DDD话语“聚合”中的伪创新
DDD话语中也有“聚合”,不过用词是Aggregate,指整个聚合/组合结构,严格一点可称为“聚合体”,而扮演整体的对象被称为“聚合根(Aggregate Root)”。前文所说的UML“聚合”,用词是Aggregation,指两个类之间的整体-部分关联,严格一点可称为“聚合关联”。
我们以图8-132中Grady Booch关于系统结构的一段隐喻作为素材,画图并标出用语的区别,如图8-133。
图8-132 摘自《面向对象分析与设计(原书第2版)》,Grady Booch 著;冯博琴 等 译,英文原版出版于1994年
图8-133 用语的区别
看起来好像只是用词的变化,但细究起来并不那么简单。
Aggregate的问题
在面向对象建模领域,Aggregate并非Eric Evans在“Domain-Driven Design”书中首先使用。在1999年出版的《UML参考手册》第1版中就有“aggregate”词条,但只有一句话,大量的内容放在了“aggregation”词条下面。
图8-134 摘自《UML参考手册》,James Rumbaugh 等 著,姚淑珍 等 译,原书出版于1999年。
UML把重点放在Aggregation上,即放在两个类之间的关系(图的边)上。图8-133中,“植物”和“根”、“茎”、“叶”存在组合(聚合)关联,说明可能会存在“植物”对象,它的组成部件是“根”、“茎”、“叶”对象。
而DDD强调Aggregate和Aggregate Root,把重点放在了类(图的结点)上,这就带来了问题。
我们来看“Domain-Driven Design: Tackling Complexity in the Heart of Software”原书的文字:
An AGGREGATE is a cluster of associated objects that we treat as a unit for the purpose of data changes. Each AGGREGATE has a root and a boundary. ……The root is the only member of the AGGREGATE that outside objects are allowed to hold references to, ……
一个AGGREGATE是一簇相关联的对象,我们把它作为数据变化的单元来对待。每个AGGREGATE有一个根和一个边界。……根是AGGREGATE的成员中唯一允许外部对象持有引用的,……
以图8-133为例,按照这个说法,我们可以说:
(“植物” “根” “茎” “叶”)等一簇相关联的对象形成了一个Aggregate,其中“植物”是该Aggregate的Aggregate Root。
问题来了。
首先,“植物”已经包括“根”、“茎”、“叶”等部件在内,不能也不需要再和这些部件并列。
如图8-135,说“植物对象是一个Aggregate”还可以说得通,但说“植物、根、茎、叶等对象一起组成一个Aggregate”是不合适的。
图8-135 对象不需要也不能和其部件并列
在类级别,说“植物和根、茎、叶是整体-部分关联”,可以。
在对象级别,说“某个植物对象由若干根、茎、叶对象组成”,可以。
但是,说“某个植物对象和它的根、茎、叶一起组成***”,不可以。
要把这个圆过去,可以把“植物”排除在组成Aggregate的“一簇相关联的对象”之外,说“一簇根、茎、叶对象组成了植物Aggregate”,不过,Eric Evans又说了“根是AGGREGATE的成员”,看来是圆不过去了。
即使“植物对象是一个Aggregate”可以说得通,这样的说法还是存在问题。
有哪一个对象不是Aggregate呢?
类和它的属性的关系可以看作组合关联,类可以在和其他类的组合关联中扮演整体的角色,类可以在和其他类的组合关联中扮演部分的角色。
仍旧以图8-132作为素材,进一步画出图8-136。
图8-136 既扮演整体又扮演部分
所谓整体、部分只是对象在关联中扮演的角色,就像合同的甲方、乙方一样。一个对象可能由很多部件组成,同时它又可以成为更大对象的部件。
离开特定的关联,指着一个对象说它是整体、Aggregate或Aggregate Root,是不合适的,除非只存在一级的整体-部分结构,这也是现在DDD实践中Aggregate的现状——再多一级的话,不妨祭出“性能”遮羞布遮掩过去——“这得加载多少数据啊,会影响性能的!”。
即使只存在一级的整体-部分结构,也没有必要在类上标注“Aggregate Root”,或者圈一个边界,关系上的菱形标记已经提供了足够的信息。
如图8-137,类的《Aggregate Root》构造型以及圈的边界都是冗余的。不过,《Aggregate Root》虽然冗余,至少在模型中记录了清晰的信息,可以映射代码或指导编码,而圈一个边界除了意念之外,并没有办法落到模型上。
图8-137 冗余的标记
不存在Aggregate Root
Aggregate Root应该是Eric Evans的造词,“Domain-Driven Design”这本书之前的软件开发书籍中,并没有出现Aggregate Root的说法。
这是一个伪创新。
对象就是由其部件(包括属性值和其他对象)组成的,把部件去掉,对象就什么都不剩了,哪里还有什么根?
之所以会有Aggregate Root这样的错觉,有可能是受了关系数据库思考方式的影响。
图8-133的类图,如果用关系数据库来保存对象,可能会得到图8-138的几个表,“植物”可不就是Aggregate Root嘛!
图8-138 受关系数据库思考方式影响的Aggregate Root错觉
Aggregate Root错觉另一个可能的原因来自人类社会。
上一小节中,曾用部门结构来类比组合(聚合)。我们说到:
给各部门分配大任务,部门把任务分解,再分配给部门内的各小组……
这样的说法符合面向对象的思考,却与人类社会现实不符。
人类社会中,部门、小组不是生命体。在现实中,我们观察不到一个巨人叫“项目部”,向着另一个巨人“财务部”吼一声,“喂,帮我结算这批临时工的工资!”,我们观察到的是一个生命体“项目部经理”向另一个生命体“财务部经理”发出请求,这样的场景也会诞生Aggregate Root的错觉。(类似问题在本书的业务建模章节中也有描述。)
图8-139 现实中观察不到这样的现象
而在面向对象的世界中,对象是有“生命”的,“项目部”向“财务部”发消息即可,并没有“项目部经理”和“财务部经理”。
/*--------------------
实际上,当前的绝大多数信息系统中,和现实中有生命的“人”对应的类,与其相关的逻辑所占比例是非常少的。封装复杂逻辑的类,我们需要为之画出复杂状态机的类,往往是“订单”、“设备”、“房间”等,它们在现实中对应的是无生命的事物。
即使将来信息系统发展到更高复杂度,“人”相关的逻辑所占比例依然会很少。人类建造的信息系统,封装的是人类对宇宙万事万物(当然包括人类自身)的认识,而人类自身(甚至包括其他生命体在内)在这万事万物中能占多少比例呢?
如果现实中没有生命,在信息系统里也没有“生命”的话,系统中应该只有映射“人”的类才配拥有操作,只有“人”才配作为所谓的“聚合根”了。
--------------------*/
Root(根)的用词不当
整体是部分们的根,这个说法是不恰当的。我们可以看看下面的表述:
植物由根、茎、叶组成,所以,植物是根……的根?
汽车由发动机、车身、底盘组成,所以,汽车是发动机……的根?
墙由砖垒成,所以,墙是砖的根?
分子由原子组成,所以,分子是原子的根?
Eric Evans的葡萄隐喻错误
以下是小问题。
软件开发中,比UML、DDD更早使用Aggregate这个词的是SQL,各种Aggregate函数如AVG()、MAX()、MIN(),针对表中的各行做各种统计。Aggregate这个用词经常会让人产生错觉,以为是“累计”或“集合”。
(正如前文所言,我的观点是UML也应该不使用Aggregate、Aggregation等词。)
Eric Evans在“Domain-Driven Design”书中用一串葡萄来隐喻Aggregate,可能在选择隐喻的时候就有这样的错觉。
图8-140 摘自Domain-Driven Design: Tackling Complexity in the Heart of Software, Eric Evans , 2003
一串葡萄就算有一亿颗,也只是同一个类“葡萄”的对象集合,这样的整体-部分关联意义并不大。
8-141 这个意义不大
若干颗葡萄、若干个煎蛋、若干根油条……组成一份早餐,其中维生素C的质量不得少于蛋白质质量的1%……这个更有意义,如图8-142。
(注:该图仅从图8-140的葡萄延伸以作说明,图中的领域知识未经过调研,“葡萄”、“煎蛋”、“0.01”等抽象程度也不够,烦请忽略这些问题。)
图8-142 更有意义的整体-部分关联
在8.2.4.7类命名用单数一节中说过,不需要对纯粹的对象集合(例如“顾客们”)建模。如果某个对象集合组成了另一个类的对象,那么这个类应该还有会其他属性(或关联)。
常被用于举例的“订单”和“订单项”,平时我们看到的例子可能是这样:
图8-143 常见的组合(聚合)类图
但这只是简化版,如果仅是这样,“订单”就没有必要存在了。之所以需要“订单”的概念,是因为“订单”还有“下单日期”、“顾客”、“收货地址”等属性,如图8-144。
图8-144 扮演整体的类还会有其他属性
另外,Eric Evans选用这个葡萄图片,可能还搞错了另一个知识,不过这个知识不是软件开发知识,而是植物学知识。
植物学上有聚合果(Aggregate Fruit)的概念,如图8-145:
图8-145 摘自百度百科“聚合果”词条
Eric Evans可能想到“Aggregate Fruit”这个术语,觉得葡萄是成串的,以为葡萄是“Aggregate Fruit”,于是把图片放上去了。
其实葡萄是单果,如图8-146。
图8-146 摘自https://zhuanlan.zhihu.com/p/37538771
总结
其实,Eric Evans只需要在之前已有的概念基础上,添加自己关于整体-部分结构的见解,不管是从核心域逻辑的角度谈责任分配,还是从性能的角度谈实现套路,不需要Aggregate和Aggregate Root这两个概念,特别是后一个,是不存在的。
[新增EA028高压注射器]24套UML EA和StarUML的建模示范视频-全程字幕(2022.7.4更新)
7月28-31晚网课:软件需求设计方法学全程实例剖析
《软件方法》书中自测题-题目全文 分卷自测(1-8章)16套111题
《软件方法》强化自测题集110题
CTO也糊涂的常用术语:功能模块、业务架构、用户需求……[20210217更新]
如何选择UMLChina服务
扫码加作者微信: