《软件方法(下)》8.3.4.6 DDD话语“聚合”中的伪创新(2)

2024-06-03 11:20

本文主要是介绍《软件方法(下)》8.3.4.6 DDD话语“聚合”中的伪创新(2),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

DDD领域驱动设计批评文集

做强化自测题获得“软件方法建模师”称号

《软件方法》各章合集


《软件方法》最新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

这篇关于《软件方法(下)》8.3.4.6 DDD话语“聚合”中的伪创新(2)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1026847

相关文章

Python版本信息获取方法详解与实战

《Python版本信息获取方法详解与实战》在Python开发中,获取Python版本号是调试、兼容性检查和版本控制的重要基础操作,本文详细介绍了如何使用sys和platform模块获取Python的主... 目录1. python版本号获取基础2. 使用sys模块获取版本信息2.1 sys模块概述2.1.1

Python实现字典转字符串的五种方法

《Python实现字典转字符串的五种方法》本文介绍了在Python中如何将字典数据结构转换为字符串格式的多种方法,首先可以通过内置的str()函数进行简单转换;其次利用ison.dumps()函数能够... 目录1、使用json模块的dumps方法:2、使用str方法:3、使用循环和字符串拼接:4、使用字符

Python版本与package版本兼容性检查方法总结

《Python版本与package版本兼容性检查方法总结》:本文主要介绍Python版本与package版本兼容性检查方法的相关资料,文中提供四种检查方法,分别是pip查询、conda管理、PyP... 目录引言为什么会出现兼容性问题方法一:用 pip 官方命令查询可用版本方法二:conda 管理包环境方法

Linux云服务器手动配置DNS的方法步骤

《Linux云服务器手动配置DNS的方法步骤》在Linux云服务器上手动配置DNS(域名系统)是确保服务器能够正常解析域名的重要步骤,以下是详细的配置方法,包括系统文件的修改和常见问题的解决方案,需要... 目录1. 为什么需要手动配置 DNS?2. 手动配置 DNS 的方法方法 1:修改 /etc/res

JavaScript对象转数组的三种方法实现

《JavaScript对象转数组的三种方法实现》本文介绍了在JavaScript中将对象转换为数组的三种实用方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友... 目录方法1:使用Object.keys()和Array.map()方法2:使用Object.entr

SpringBoot中ResponseEntity的使用方法举例详解

《SpringBoot中ResponseEntity的使用方法举例详解》ResponseEntity是Spring的一个用于表示HTTP响应的全功能对象,它可以包含响应的状态码、头信息及响应体内容,下... 目录一、ResponseEntity概述基本特点:二、ResponseEntity的基本用法1. 创

java中判断json key是否存在的几种方法

《java中判断jsonkey是否存在的几种方法》在使用Java处理JSON数据时,如何判断某一个key是否存在?本文就来介绍三种方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的... 目http://www.chinasem.cn录第一种方法是使用 jsONObject 的 has 方法

java中ssh2执行多条命令的四种方法

《java中ssh2执行多条命令的四种方法》本文主要介绍了java中ssh2执行多条命令的四种方法,包括分号分隔、管道分隔、EOF块、脚本调用,可确保环境配置生效,提升操作效率,具有一定的参考价值,感... 目录1 使用分号隔开2 使用管道符号隔开3 使用写EOF的方式4 使用脚本的方式大家平时有没有遇到自

在Ubuntu上打不开GitHub的完整解决方法

《在Ubuntu上打不开GitHub的完整解决方法》当你满心欢喜打开Ubuntu准备推送代码时,突然发现终端里的gitpush卡成狗,浏览器里的GitHub页面直接变成Whoathere!警告页面... 目录一、那些年我们遇到的"红色惊叹号"二、三大症状快速诊断症状1:浏览器直接无法访问症状2:终端操作异常

Python打包成exe常用的四种方法小结

《Python打包成exe常用的四种方法小结》本文主要介绍了Python打包成exe常用的四种方法,包括PyInstaller、cx_Freeze、Py2exe、Nuitka,文中通过示例代码介绍的非... 目录一.PyInstaller11.安装:2. PyInstaller常用参数下面是pyinstal