DDD领域驱动设计批评文集
做强化自测题获得“软件方法建模师”称号
《软件方法》各章合集
8.3 建模步骤C-2 识别类的关系
8.3.4 识别关联关系
8.3.4.2 关联的进一步细分
是否进一步细分各种关联,各种面向对象方法学观点不同。有的认为关联就是关联,不用再细分,有的则认为需要进一步细分。
例如,James J. Odell就把聚合分为6种并详细讨论,如图8-122。
图8-122 摘自Journal Of Object-Oriented Programming Vol 5, No 8. , James J. Odell , 1994
UML规范采取的是中间路线,把关联分为三种:普通关联、聚合(Aggregation)和组合(Composition)。
用图形表示,普通关联是一根直线,聚合有一端是空心菱形,组合有一端是实心菱形,如图8-123。
图8-123 UML三种关联的图示
在UML元模型中,把它们视为属于三个不同的AggregationKind,如图8-124。
图8-124 三个AggregationKind
从元模型上看,“聚合”应该叫作“分享型聚合”,“组合”应该叫作“组合型聚合”,但本书还是使用“聚合”、“组合”,原因阅读后文自知。
聚合和组合都表示“整体-部分”关联,在类图中,菱形一端表示整体,另一端表示部分。
相对于聚合,组合还有两条额外的约束:
(1)在同一时刻,部分对象只属于一个整体对象;
(2)整体对象被销毁,部分对象也要销毁;
虽然UML定义了聚合的概念,但实践中要不要使用聚合,经常会引起争论。在聚合关联中,部分对象同一时刻可以被多个整体对象共享,使得“整体-部分”的概念变得模糊,和普通关联难以区分。
James Rumbaugh等人在《UML参考手册(第2版)》中认为聚合是建模的“安慰剂”。
图8-125 摘自Unified Modeling Language Reference Manual, 2nd Edition, James Rumbaugh, Ivar Jacobson, Grady Booch, 2004
Craig Larman认为不需要使用聚合,在合适的情况下使用组合即可。
图8-126 摘自Applying UML and Patterns: An Introduction to Object-Oriented Analysis and Design and Iterative Development, Third Edition, Craig Larman, 2004
8.3.4.3 关于“整体-部分”结构
之所以在关联关系中进一步划分出一个“整体-部分”关联,是希望把小的对象进一步组装成更大的对象,以获得更大的复用单元。
如果把关联定义为“整体-部分”的关联,意味着部分对象成为整体对象的部件,外部的对象不能发消息给部分对象,只能发给整体对象,再由整体对象分解和分配给组成它的部分对象,如图8-127所示。
图8-127 “整体-部分”关联影响责任分配
类图上有很多类,类之间的密切程度会有所不同。如果根据目前责任分配的情况,判断某些类之间协作的频率远超过它们和外部其他类协作的频率,而且预判将来也可能是这样,那么通过建立组合关联来强制把它们封装成一个整体来分配责任,是合算的。
而“目前责任分配的情况”也不是随意得到的,需要结合类的属性和关联来分配。这个过程会包括多次互相尝试和互相验证,详细内容在行为建模部分再讲述。
和划分部门类比
建立组合关联和公司的部门划分有类似之处。
公司不划分部门,老总一个个员工派任务也能达到目标,只是效率不高,而且不管出现什么变化都要打开老总的“代码”来修改。
划分部门之后,老总就省心多了,只需要给各部门分配大任务,部门把任务分解,再分配给部门内的各小组,各小组再把任务分解,分配给小组内的小小组……这样,各种逻辑就会分散到各个部门、小组、小小组……
当然,这是有代价的。划分部门之后,上级就不要越过下级去找更下级,下级也不能想找谁就找谁,都要讲基本法。
如果部门内各下级之间的协作频率远高于和其他部门协作的频率,说明这样的代价是值得付出的,部门划分以及责任的分解和分配是合理的。反之则说明不合理。
本书建议(1):
在没有足够证据的情况下,一律使用普通关联,不用组合(聚合)关联。
只有经过序列图、状态机图等进一步建模核心域逻辑之后,有足够证据支持定义为组合(聚合)关联更有利,才定义组合(聚合)关联用于指导后续其他用例的责任分配。
经常看见有“架构师”随意使用组合,图8-128是一张学员发给我评点的类图。可以看到,图上到处都是菱形。
图8-128 一张带有大量菱形的类图(类的信息已隐去)
如果公司老总在没有充分调研员工能力以及公司业务的情况下,着急过一把官瘾,胡乱划分部门,提拔干部,会大大损害所有人的利益,很容易激起反抗。
然而,如果“架构师”因为偷懒或炫耀,胡乱定义组合(聚合)关联,并不会激起各个代码片段的反抗。计算机程序目前还没有产生自我意识,没有Neo(电影“The Matrix”,《黑客帝国》),特别乖,爱怎么整都可以。
如图8-129,如果建模为普通关联,还得给关联想个合适的名字。算了,懒得想,貌似说“订单有顾客”也说得通嘛,“有”那不就是组合(聚合)吗?干脆加个菱形吧,这样还省事,而且相对于一根直线,菱形让人有高大上的感觉!
图8-129 为了偷懒滥用组合(聚合)
最近一些年,由于DDD话语对“聚合”过度吹嘘,某些软件开发人员把“划分聚合”看成“有架构师能力”的表现,于是在没有足够证据的情况下,兴奋地把“聚合”到处用——哈哈,我会切割系统了,我架构师了!
这些人的思维经常是颠倒的:先拍脑袋定“聚合”,然后就按DDD话语的建议来使用,包括外部对象的访问、创建、访问数据等,然后再用实现的代码(show me the code嘛)来“证明”之前划分的“聚合”是正确的,形成“完美”闭环。
用公司类比,相当于公司老总拍脑袋把张三、李四、王五等人划分成一个部门,并任命张三为部门领导,然后通过张三发号施令,再用这个“事实”来“证明”张三作为部门领导是正确的。当然,这样类比不完全贴切,后文批评“聚合根”时还会提到。
本书建议(2):
有必要表达“整体-部分”关联时,仅使用组合,不使用聚合。这一点和Larman是一致的。
我用下面的例子来说明本书的两条建议:
如图8-130,把“微信群”和“微信账户”建模为聚合,而且多重性为多对多。理由是“微信群有微信账户”,而且微信群解散,微信账户还在。
图8-130 含糊的聚合
在没有足够证据时,应该建模为普通关联,如图8-131:
图8-131 尽量使用普通关联
如果一定要使用“整体-部分”关联,使用如图8-132的组合。
图8-132 使用组合
图8-132将“微信群员”和“微信账户”分离,“微信群员”仅属于一个“微信群”。如果“微信群”对象消失,“微信群员”对象及相关属性值也就消失了,但“微信账户”还在。
注意,即使是图8-132,也要有足够证据,而不是为了偷懒和炫耀。
如果只是玩文字游戏,图8-132也可以变成图8-133,从另一个角度来“组合”似乎也未尝不可。
图8-133 另一个角度的组合