《软件方法(下)》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清空Word段落样式的三种方法

《Python清空Word段落样式的三种方法》:本文主要介绍如何用python-docx库清空Word段落样式,提供三种方法:设置为Normal样式、清除直接格式、创建新Normal样式,注意需重... 目录方法一:直接设置段落样式为"Normal"方法二:清除所有直接格式设置方法三:创建新的Normal样

在Linux系统上连接GitHub的方法步骤(适用2025年)

《在Linux系统上连接GitHub的方法步骤(适用2025年)》在2025年,使用Linux系统连接GitHub的推荐方式是通过SSH(SecureShell)协议进行身份验证,这种方式不仅安全,还... 目录步骤一:检查并安装 Git步骤二:生成 SSH 密钥步骤三:将 SSH 公钥添加到 github

把Python列表中的元素移动到开头的三种方法

《把Python列表中的元素移动到开头的三种方法》在Python编程中,我们经常需要对列表(list)进行操作,有时,我们希望将列表中的某个元素移动到最前面,使其成为第一项,本文给大家介绍了把Pyth... 目录一、查找删除插入法1. 找到元素的索引2. 移除元素3. 插入到列表开头二、使用列表切片(Lis

Python安装Pandas库的两种方法

《Python安装Pandas库的两种方法》本文介绍了三种安装PythonPandas库的方法,通过cmd命令行安装并解决版本冲突,手动下载whl文件安装,更换国内镜像源加速下载,最后建议用pipli... 目录方法一:cmd命令行执行pip install pandas方法二:找到pandas下载库,然后

Linux系统中查询JDK安装目录的几种常用方法

《Linux系统中查询JDK安装目录的几种常用方法》:本文主要介绍Linux系统中查询JDK安装目录的几种常用方法,方法分别是通过update-alternatives、Java命令、环境变量及目... 目录方法 1:通过update-alternatives查询(推荐)方法 2:检查所有已安装的 JDK方

SQL Server安装时候没有中文选项的解决方法

《SQLServer安装时候没有中文选项的解决方法》用户安装SQLServer时界面全英文,无中文选项,通过修改安装设置中的国家或地区为中文中国,重启安装程序后界面恢复中文,解决了问题,对SQLSe... 你是不是在安装SQL Server时候发现安装界面和别人不同,并且无论如何都没有中文选项?这个问题也

Java Thread中join方法使用举例详解

《JavaThread中join方法使用举例详解》JavaThread中join()方法主要是让调用改方法的thread完成run方法里面的东西后,在执行join()方法后面的代码,这篇文章主要介绍... 目录前言1.join()方法的定义和作用2.join()方法的三个重载版本3.join()方法的工作原

在MySQL中实现冷热数据分离的方法及使用场景底层原理解析

《在MySQL中实现冷热数据分离的方法及使用场景底层原理解析》MySQL冷热数据分离通过分表/分区策略、数据归档和索引优化,将频繁访问的热数据与冷数据分开存储,提升查询效率并降低存储成本,适用于高并发... 目录实现冷热数据分离1. 分表策略2. 使用分区表3. 数据归档与迁移在mysql中实现冷热数据分

Spring Boot从main方法到内嵌Tomcat的全过程(自动化流程)

《SpringBoot从main方法到内嵌Tomcat的全过程(自动化流程)》SpringBoot启动始于main方法,创建SpringApplication实例,初始化上下文,准备环境,刷新容器并... 目录1. 入口:main方法2. SpringApplication初始化2.1 构造阶段3. 运行阶

Olingo分析和实践之ODataImpl详细分析(重要方法详解)

《Olingo分析和实践之ODataImpl详细分析(重要方法详解)》ODataImpl.java是ApacheOlingoOData框架的核心工厂类,负责创建序列化器、反序列化器和处理器等组件,... 目录概述主要职责类结构与继承关系核心功能分析1. 序列化器管理2. 反序列化器管理3. 处理器管理重要方