《软件方法》最新pdf和epub文件:umlchina.com/url/softmeth2024.html
8.3 建模步骤C-2 识别类的关系
8.3.4 识别关联关系
8.3.4.6 DDD话语“聚合”中的伪创新
(3)aggregate root是伪创新
(续前文)
aggregate root错觉另一个可能的原因来自人类社会的直觉。
在8.3.4.3 关于“整体-部分”结构中,曾用部门结构来类比组合(聚合):
给各部门分配大任务,部门把任务分解,再分配给部门内的各小组……
这样的说法符合面向对象的思考,却与人类观察这个世界的直觉不符。
在直觉上,我们并不是观察到一个叫“项目部”的巨人,向着另一个巨人“财务部”吼一声,“喂,帮我结算这批临时工的工资!”,我们观察到的是一个人“项目部经理”向另一个人“财务部经理”发出请求“喂,帮我结算我部门这批临时工的工资!”
图8-146 人类直觉观察不到这样的现象
因为作为观察者的我们是人,或者说,是一个人脑系统,所以观察者直觉上得到的第一个抽象是和自己同一级别的抽象,即系统和系统的交互:项目部经理-财务部经理。
这样的场景也会诞生类似aggregate root的错觉,“项目部经理”是“项目部”的根,“财务部经理”是“财务部”的根,他们负责和其他部门打交道。
而在面向对象的世界中,对象是有“生命”和“智慧”的,“项目部”可以像人一样,向“财务部”发消息。
即使部门中确实有经理职位而且系统需要维护这个信息,在分配责任的时候,也并不需要把“经理”对象作为责任分配的起点。
实际上,当前的绝大多数信息系统中,和现实中有生命的“人”对应的类,与其相关的逻辑所占比例非常少。承担系统关键责任、封装复杂逻辑、我们需要为之画出复杂状态机的类,往往在现实中对应的是无生命的事物,例如“订单”、“设备”、“房间”等。
即使将来信息系统发展到更高复杂度,“人”相关的逻辑所占比例依然会很少。人类建造的信息系统,封装的是人类对宇宙万事万物(当然包括人类自身)的认识或想象,而人类自身(甚至包括其他生命体在内)的内容在这宇宙万事万物中能占多少比例呢?
如果现实中没有生命,在信息系统里也没有“生命”的话,系统中应该只有映射“人”的类才配拥有操作,只有“人”才配作为所谓的“聚合根”了——这种现象在面向对象的初学者中很常见,例如认为“借书”是“馆员”的责任。
此处还有一个有趣的问题,留个读者思考:
假设一定要推行“聚合根”的革命性创造,根据上文所说,我们直觉的级别,是系统和系统的交互:项目部经理<->财务部经理。
如果向外一个级别,可以得到组织和组织的交互:项目部<->财务部。
如果向内一个级别,可以得到系统部件和系统部件的交互:项目部经理的嘴巴<->财务部经理的耳朵。
那么,“聚合根”项目部经理代表的是项目部还是项目部经理的嘴巴呢?
(4)associated的误用
我们第三次看“Domain-Driven Design”中的描述:
An AGGREGATE is a cluster of associated objects
一个AGGREGATE是一簇相关联的对象
前文讲到关联(Association)的概念时说过,关联是系统需要维护的关系。
我们用人来举例,如果是“a cluster of associated objects(一簇相关联的对象)”,类图可能如图8-147,各个部件之间存在关联:
图8-147 部件之间存在关联
虽然现实中的人体结构有可能是像图8-147这样的,但目前我们的软件结构更倾向于像图8-148这样,部件只和整体关联,部件之间(最好)不存在关联:
图8-148 部件只和整体关联
可能有的读者会想,图8-147有道理呀,这些部件之间有“兄弟”或“同事”关联呀!其实这些“兄弟”或“同事”是可以从整体-部分关联推算出来的冗余概念,系统不需要维护。
我们可以对比一下Vaughn Vernon在他的“Implementing Domain-Driven Design”[Vernon 2013]中的用词:
Is an Aggregate just a way to cluster a graph of closely related objects under a common parent? If so, is there some practical limit to the number of objects that should be allowed to reside in the graph? Since one Aggregate instance can reference other Aggregate instances, can the associations be navigated deeply, modifying various objects along the way?
聚合只是把在共同父对象之下紧密相关的对象图聚集在一起的一种方式吗?如果是这样,对于允许驻留在该图上的对象数目,有没有一些实际的限制?既然一个聚合实例可以引用其他聚合实例,那么关联是否可以向深处导航,沿途修改各种对象呢?
★“Implementing Domain-Driven Design”的中译本《实现领域驱动设计》在此处的翻译存在严重错误,参见《实现领域驱动设计》的译者其实没错?(二)。
Vaughn Vernon在此处用词非常准确。
描述“一簇对象”时,他用的词是“related(相关的)”。
怎么个相关?静态上,它们都是同一个整体的部件,动态上,它们中的部分或全部会在某个场景中由整体对象协调来完成任务,如图8-149:
图8-149 部件在整体的协调下协作完成任务
同一段文字中,Vernon也在正确的地方使用了“association(关联)”一词,讨论沿着关联关系导航的深度问题。
读者可以回看8.3.1 类的关系中与“关系(relationship)”和“关联(association)”相关的内容,以对比related和associated。
(5)Eric Evans的葡萄隐喻错误
软件开发中,比UML、DDD更早使用Aggregate这个词的是SQL,各种Aggregate函数如SUM()、AVG()、MAX()、MIN(),用于统计表中的数据。Aggregate这个用词经常会让人产生错觉,以为是“累计”或“集合”。
Eric Evans在“Domain-Driven Design”书中用一串葡萄来隐喻Aggregate,如图8-150。这个隐喻的选择是有问题的,有可能把“聚合”当成了“集合”。
图8-150 摘自Domain-Driven Design: Tackling Complexity in the Heart of Software, Eric Evans , 2003
一串葡萄就算有一亿颗,也只是同一个类“葡萄”的对象集合。在8.2.4.7 类命名用单数一节中说过,不需要对纯粹的对象集合建模。
8-151 不需要建模纯粹的“葡萄集合”
★当然,此处适合践行某些领域驱动设计实践投资少、见效快、产量高、门槛低、仪式感十足的精神,可以把每个类都无条件地配一个“**们”或“**s”,类的个数瞬间翻倍。
如果一定要以葡萄做素材,以下这个隐喻可能更合理:
若干颗葡萄、若干个煎蛋、若干根油条……组成一份早餐,其中维生素C的质量不得少于蛋白质质量的1%……这个更有意义,如图8-152。
(注:该图仅从图8-150的葡萄延伸以作说明,图中的领域知识未经过调研,“葡萄”、“煎蛋”、“0.01”等抽象程度也不够,烦请忽略这些问题。)
图8-152 更合理的隐喻
如果“对象集合”另有含义,演化成了另一个类的对象,那么这个“另一个类”不会仅仅是一个纯粹的集合,应该还会封装其他内容。
常被用于举例的“订单”和“订单项”,平时我们看到的例子可能如图8-153:
图8-153 常见的订单-订单项类图
但这只是简化版,如果仅是这样,“订单”就没有必要存在了。之所以需要“订单”的概念,是因为还有“下单日期”、“顾客”、“收货地址”等概念,要通过“订单”组织起来,如图8-154。
图8-154 扮演整体的类还会封装其他知识
Eric Evans选用这个葡萄图片,可能还搞错了另一个知识。这个知识不是软件开发知识,而是植物学知识。
植物学上有聚合果(Aggregate Fruit)的概念,如图8-155:
图8-155 摘自百度百科“聚合果”词条
Eric Evans可能想到“Aggregate Fruit”这个术语,觉得葡萄是成串的,以为葡萄是“Aggregate Fruit”,于是把图片放上去了。
其实葡萄是单果,如图8-156。
图8-156 摘自https://zhuanlan.zhihu.com/p/37538771