65.9K
CodeProject 正在变化。 阅读更多。
Home

真正理解关联、聚合和组合

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.92/5 (20投票s)

2015年2月26日

CPOL

8分钟阅读

viewsIcon

36885

这是对“理解关联、聚合和组合”一文的补充,修正了原文章中这三个概念的解释各存在的一个缺陷。

引言

我写这篇文章是为了纠正原文章中关于关联、聚合和组合这三个概念的解释中各存在的一个缺陷(截至 2015 年 2 月 25 日),原文章链接为:Understanding Association, Aggregation, and Composition。作者的解释并没有反映出在 UML 定义的基础上,面向对象社区对这些术语的普遍理解。

背景

在本节中,我将根据我的教程书籍《Building Front-End Web Apps with Plain JavaScript》中的解释,总结 UML 中关联、聚合和组合的定义。

请注意,在 UML 中,聚合组合都被定义为关联的特殊形式,更准确地说,是部分-整体关联的特殊形式。鉴于关联的概念本身就很难理解(正如我在我的文章 Why are associations so difficult to understand, not just for developers? 中尝试解释的那样),难怪关于聚合组合这两个 UML 概念的含义会引起相当大的困惑。

关联

单向功能关联对应于面向对象编程语言中的(单值)引用属性,以及 SQL 表中的外键

带有引用属性 Committee::chair 指向类 ClubMember 的类图我们可以通过两个类图来阐释这种对应关系。在右侧的第一个图表中,`Committee` 类有一个单值引用属性 `chair`,它指向 `ClubMember` 类。

类 Committee 和 ClubMember 之间带有 chair 关联的类图 在右侧的第二个图表中,单值引用属性 `chair` 以 `Committee` 和 `ClubMember` 类之间相应的单向功能关联的形式进行了可视化展示。注意关联末端上带有“chair”标注的点。这个点表示关联末端由 `Committee` 拥有,这与 `chair` 是 `Committee` 的引用属性的先前模型等效。

通常,关联是具有两个或多个对象类型参与的关系类型。两个对象类型之间的关联称为二元。在本文中,我们仅讨论二元关联。为简化起见,当我们说“关联”时,实际是指“二元关联”。

对象类型之间的关联对这些类型对象的实例之间的关系进行分类。例如,关联Committee-has-ClubMember-as-chair(在上面的类图中可视化为连接线)可以对 FinanceCommittee-has-PeterMiller-as-chair、RecruitmentCommittee-has-SusanSmith-as-chair 和 AdvisoryCommittee-has-SarahAnderson-as-chair 这样的关系进行分类,其中 PeterMiller、SusanSmith 和 SarahAnderson 对象属于 `ClubMember` 类型,而 FinanceCommittee、RecruitmentCommittee 和 AdvisoryCommittee 对象属于 `Committee` 类型。

聚合

聚合是部分-整体关联的一种特殊形式,其中一个整体的组成部分可以与其他整体共享。例如,我们可以对 `DegreeProgram` 和 `Course` 类之间的聚合进行建模,如下所示,因为一门课程是一个学位项目的一部分,并且一门课程可以被两个或多个学位项目共享(例如,工程学位可以与计算机科学学位共享一门 C 语言课程)。

enter image description here

然而,带有可共享部分的聚合概念实际上意义不大,因此它对实现没有任何影响,许多开发者因此更倾向于在类图中不使用白色的菱形,而是仅将其建模为普通关联。UML 规范说:“共享聚合的精确语义因应用领域和建模者而异。”

聚合在整体端的多重性可以是任意数字(*),因为一个部分可以属于任意数量的整体,或在任意数量的整体之间共享

组成

关于部分-整体关联概念聚合组合之间区别的困惑竟然如此之多。主要问题在于普遍存在的误解(即使是在专家级软件开发人员和 UML 作者之间),即组合的概念暗示着整体与其部分之间存在生命周期依赖关系,以至于这些部分在没有整体的情况下无法存在。但这种观点忽略了一个事实,即也存在部分-整体关联的情况,其部分不可共享,并且在整体被销毁后仍然可以存在。

在 UML 规范文档中,“组合”一词的定义始终包含不可共享的部分,但它并没有清楚地说明“组合”的定义特征是什么,哪些仅仅是可选特征。即使在最新版本(截至 2015 年)UML 2.5 中,在试图改进“组合”一词的定义后,它仍然含糊不清,并且没有提供任何关于如何建模不可共享部分的部分-整体关联的指导,这些部分可以从整体中分离出来并在整体销毁后生存下来,与那些不能分离且随整体一起被销毁的情况形成对比。他们说:

引用

如果一个复合对象被删除,其所有作为对象的组成部分实例也将随之被删除。

但同时他们也说:

引用

在复合对象被删除之前,组成部分对象可以从复合对象中移除,因此不会作为复合对象的一部分被删除。

这种混淆表明 UML 定义的不足,它没有考虑到组件和复合体之间的生命周期依赖关系。因此,重要的是要理解如何通过引入一个 UML 构造型 <<不可分离>> 来增强 UML 定义,其中组件不能从其复合体中分离出来,因此,当其复合体被销毁时,它们也必须被销毁。

正如 Martin Fowler 所解释的,区分组合的主要问题在于“一个对象只能是一个组合关系的一部分”(Geert Bellekens 在精彩的博客文章 UML Composition vs Aggregation vs Association 中也解释了这一点)。除了组合的这一定义特征(拥有排他性不可共享的部分)之外,组合还可能伴随复合体与其组件之间的生命周期依赖关系。事实上,存在两种此类依赖关系:

  1. 当一个组件必须始终连接到复合体时,换句话说,当它有一个强制性复合体时,如组合线复合体端上的“正好一个”多重性所表达的,那么当其当前复合体被销毁时,它必须被重用(或重新连接)到另一个复合体,或者被销毁。这可以通过下面图表中所示的 `Person` 和 `Heart` 之间的组合来说明。当一个人死亡时,他的心脏要么被销毁,要么被移植给另一个人。
  2. 当一个组件无法从其复合体中分离出来时,换句话说,当它是不可分离的时,那么,并且只有在这种情况下,组件在其复合体被销毁时才必须被销毁。具有不可分离部分的组合的一个例子是 `Person` 和 `Brain` 之间的组合。

enter image description here

总而言之,生命周期依赖关系仅适用于组合的特定情况,而不是普遍适用。因此,它们不是定义特征。UML 规范指出:“在复合实例被删除之前,组成部分实例可以从复合实例中移除,因此不会作为复合实例的一部分被删除。”在 `Car`-`Engine` 组合的例子中,如下图所示,引擎在汽车被销毁之前显然可以从汽车(复合体)中分离出来,在这种情况下,引擎不会被销毁,并且可以被重用。这暗示着组合线复合体端的零个或一个多重性。

enter image description here

组合在复合体端的关联末端的多重性是 1 或 0..1,具体取决于组成部分是否有强制性复合体(必须连接到复合体)。如果组成部分是不可分离的,这意味着它们有一个强制性复合体。

错误纠正

错误 1:关联是关于对象相互使用,但具有自己的对象生命周期

参与关联的对象之间没有必要“相互使用”。实际上,两个类之间的关联不是通过某些方法参数建立的,正如文章中提出的例子所暗示的那样,而是通过引用属性(类属性)建立的,其范围/类型是相关类。如果一个方法参数的类型是一个类,这并不建立一个关联,而是建立一个依赖关系。

关联不暗示,也不排除,相关/链接对象之间的任何生命周期依赖关系。

错误 2:在聚合中,子对象属于单个父对象

在 UML 中,聚合被定义为一种特殊形式的关联,其预期含义是部分-整体关系,其中一个整体的组成部分可以与其他整体共享。因此,“子对象”可以属于多个“父对象”。这可以通过下面的聚合示例来说明,其中一门课程可以属于多个学位项目。

Aggregation example

错误 3:在组合中,子对象具有所有者的生命周期

这是一个普遍存在的误解。

组合的定义特征是拥有排他性(或不可共享)的部分。组合可能伴随整体与其部分之间的生命周期依赖关系,这意味着当整体被销毁时,其所有部分也随之被销毁。然而,这仅适用于某些组合情况,而不适用于其他情况,因此它不是定义特征。一个组合的例子是,其部分(或组件)可以从整体(或复合体)中分离出来,因此在整体销毁后仍然可以生存下来,如下所示:

Comremoved example where components can survive

引擎作为汽车的组成部分,可以从其复合体汽车中分离出来。因此,在这种情况下,组件不具有与其复合体相同的生命周期。在其前任所有者的生命周期结束后,它可以被重用于另一辆汽车。

© . All rights reserved.