关联、聚合和组合的区别是什么?

567
什么是关联、聚合和组合的区别?请从实现方面进行解释。

11
在UML 2中,不存在聚合或组合这样的元素(不过在UML 1.4中有)。相反,聚合/组合在UML 2中被实现为带有AggregationKind属性设置为Shared或Composite的关联元素。 - chickeninabiscuit
已经有很多关于聚合和组合的答案在 Stack Overflow 上了:http://stackoverflow.com/search?q=aggregation+and+composition - lothar
2
有用的文章在这里 http://www.codeproject.com/Articles/22769/Introduction-to-Object-Oriented-Programming-Concep#Composition - GibboK
6
我知道这个问题已经被回答了很多次,但我认为有关这个问题最好的解释是这个链接:http://www.holub.com/goodies/uml/#composition。 - WLin
当我们有任何对象之间的关系时,这被称为关联。聚合和组合都是关联的专业形式。组合又是聚合的专业形式。 - Raúl
21个回答

608

对于两个对象 FooBar,可以定义它们之间的关系:

关联 - 我与一个对象有关系。Foo 使用 Bar

public class Foo {         
    private Bar bar;
};

NB:请参见Fowler的定义 - 关键在于BarFoo存在语义关联,而不仅仅是依赖项(如intstring)。

组合 - 我拥有一个对象并且我对其生命周期负责。当Foo终止时,Bar也会随之终止。

public class Foo {
    private Bar bar = new Bar(); 
}

聚合 - 我有一个从别人那里借来的对象。当 Foo 销毁时,Bar 可能会继续存在。

public class Foo { 
    private Bar bar; 
    Foo(Bar bar) { 
       this.bar = bar; 
    }
}

8
看起来是 C# / Java 代码。如果是这种情况,关联和聚合代码是相同的。在两种情况下,“bar”只是被引用,Bar 对象可能会继续存在。 - Ajay
32
聚合在保持对象引用方面与关联不同,因此实现上存在差异。 - ABCD
26
“关联(Association)”比仅作为方法参数使用要强一些。我认为你的关联代码片段更对应于“依赖关系(Dependency)”。你可能需要查看Martin Fowler的相关文章。 - Ahmad Abdelghany
9
@AhmadAbdelghany是正确的。第一个例子是一种依赖关系。第三个例子适用于关联和聚合。 - André Willik Valenti
3
抱歉,但是该示例并不是一个关联,而只是一种简单的使用依赖。关联意味着存在语义关系,即连接具体实例的链接。 - Christophe
显示剩余5条评论

200

我知道这个问题被标记为 C#,但概念是非常通用的,像这样的问题会重定向到这里。因此,我将在这里提供我的观点(从 Java 角度有些偏见,因为我更熟悉 Java)。

当我们考虑面向对象的特性时,我们总是想到对象、类(对象蓝图)以及它们之间的关系。对象通过方法相互关联和交互。换句话说,一个类的对象可以使用另一个类的对象提供的服务/方法。这种关系称为关联

聚合和组合是关联的子集,这意味着它们是关联的特定情况。

enter image description here

  • 在聚合和组合中,一个类的对象“拥有”另一个类的对象
  • 但是有一个微妙的区别。在组合中,由拥有类的对象拥有的类的对象无法独立存在(也称为“死亡关系”)。它将始终作为其拥有对象的一部分而存在,而在聚合中,依赖对象是独立的,即使拥有类的对象死亡,它仍然可以存在。
  • 因此,在组合中,如果拥有对象被垃圾回收,所拥有的对象也将被回收,这在聚合中并不是这种情况。

感到困惑了吗?

组合示例:考虑汽车和仅适用于该汽车的引擎的示例(这意味着它不能用于任何其他汽车)。汽车类与特定引擎类之间的这种关系称为组合。汽车类的对象不能没有特定引擎类的对象存在,而特定引擎类的对象没有汽车类毫无意义。简单来说,汽车类完全“拥有”特定引擎类。

聚合示例: 现在考虑类Car和类Wheel。Car需要一个Wheel对象才能正常运行。意思是Car对象拥有Wheel对象,但我们不能说Wheel对象在没有Car对象的情况下没有意义。它同样可以在Bike、Truck或不同的Cars对象中使用。

总结一下 -

总之,关联是一个非常通用的术语,用于表示一个类使用另一个类提供的功能。如果一个父类对象拥有另一个子类对象,并且该子类对象在没有父类对象的情况下没有实际意义,则我们称其为组合。如果它可以,那么就被称为聚合。

更多详情请点击此处。 我是http://opensourceforgeeks.blogspot.in的作者,并已在上面添加了链接以获取更多上下文信息。


11
我原本想问你为什么要回答一个五年前就已经被回答过的问题,但接着我读了你的博客文章,发现它比这里一些答案更加详细。点赞! - Donbhupi
4
我同意 @Donbhupi 的看法,你的回答比其他很多人更详尽和正确。 - zish
2
当C#和Java开发人员声称他们在使用组合时,这只存在于这些语言的原始类型上时,这真的很有趣。如果你想真正理解组合,你必须使用C++,因为在C++中,对象可以真正成为其他对象的一部分。它们不仅仅漂浮在堆内存中并持有指向彼此的指针,然后声称存在组合关系。 - Everyone
我们能否在使用托管内存的C#代码中进行组合? - Paulo André Haacke
1
@大家,设计模式在不同的编程语言中实现可能有所不同,但它们作为“设计模式”的实用性必须来源于保持实现独立性。组合始终且定义上意味着由容器管理或通过容器进行生命周期管理。你描述的C# / Java情况可能只是聚合,其中其他人拥有“组成”对象,否则这是一种错误的实现,因为它破坏了封装性,在C ++中同样可能(例如,暴露指向所拥有对象的指针)。 - tjbtech
显示剩余8条评论

194

协会是对象之间关系的概括性概念。

组合混合)是将简单对象或数据类型封装成一个单一单元的方式。组合是许多基本数据结构的关键构建块。

聚合将多个事物形成一个集群)与普通的组合不同,它不意味着所有权。在组合中,当拥有对象被销毁时,包含的对象也会被销毁。而在聚合中,这并不一定成立。

记住区别的技巧

  • "可以调用":协会
  • "有-A":聚合
  • "分-于":组合
背景 聚合 组合
生命周期 对象有自己的生命周期,没有所有者 由整体或父对象控制
范围 父对象和子对象是独立的 父对象也意味着其子对象的消亡
关系 拥有 部分
强度 弱关系 强关系
现实生活例子 汽车和驾驶员 汽车和车轮

现在让我们观察下面的图片

relations

enter image description here

关联/聚合/组合(紧耦合):在类之间使用实线和关联箭头。这表示类之间的一般关系,一个类的变化可以直接影响另一个类。您还可以使用关联端多重性来表示关系的性质和基数。

依赖/接口或抽象类(松耦合):您可以使用虚线和箭头,从一个类指向另一个类。这表示一个类以某种方式依赖于另一个类。如果供应类的变化直接影响客户类,则依赖关系可以紧密。

类比:

组合:下面的图片是图像组合,即使用单个图像制作一张图像。
enter image description here

聚合:图像的集合在单个位置。

enter image description here

例如,一所大学拥有各种部门,每个部门都有一些教授。如果大学关闭,这些部门将不再存在,但是这些部门中的教授将继续存在。因此,大学可以被看作是部门的组合,而部门则具有教授的聚合。此外,一个教授可以在多个部门工作,但一个部门不能属于多个大学。

33
在阅读了这个话题的很多内容后,这个回答是最直观易懂的一个。应该放到维基百科上。 - Jankapunkt
7
表达得很精美。 - Sid
2
关于聚合,你说“子对象属于单个父对象”,这是不正确的。在UML中,有共享聚合的概念,即子对象可以属于多个父对象。你在关于Department作为教授的一个聚合例子中承认了这一点,因为你说一个教授可以在多个Department工作。 - www.admiraalit.nl
@aderchox 不,共享聚合意味着子项(部分)由多个父项(整体)共享,因此www.admiraalit.nl是正确的:这个答案是不正确的。 - Géry Ogam
在你的表格中,你说组合是“部分”,而聚合是“有一个”。这听起来不对,因为它意味着“A是B的一部分”,或者“汽车是轮子的一部分”。你没有保持主语所在的一致性。 - Michael Moreno
显示剩余3条评论

129
依赖关系(引用) 这意味着两个对象之间没有概念上的链接。例如,EnrollmentService对象引用了Student和Course对象(作为方法参数或返回类型)。
public class EnrollmentService {
    public void enroll(Student s, Course c){}
}

关联(拥有关系) 这意味着对象之间几乎总是存在联系(它们是关联的)。 订单对象拥有一个客户对象。
public class Order {
    private Customer customer
}

聚合(有一个+整体-部分) 一种特殊的关联方式,两个对象之间存在整体-部分的关系。尽管它们可以独立存在,但它们之间仍然存在联系。
public class PlayList {
    private List<Song> songs;
}

或者

public class Computer {
    private Monitor monitor;
}

注意:最棘手的部分是区分聚合和普通关联。老实说,我认为这是可以有不同解释的。
组合(拥有 + 整体-部分 + 所有权) 一种特殊的聚合。一个“公寓”由一些“房间”组成。一个“房间”不能没有一个“公寓”而存在。当一个公寓被删除时,所有相关的房间也会被删除。
public class Apartment{
    private Room bedroom;
    public Apartment() {
       bedroom = new Room();
    }
}

5
是的,在确定对象关系方面唯一棘手的部分就是区分关联和聚合。其他一切都很清楚。我给你点赞。 - Fouad Boukredine
2
从实现的角度来看,聚合和关联不应该是相同的吗?因为唯一的区别在于逻辑上。我认为聚合和关联可以具有类似集合的容器作为引用,因为这通常是由多重性决定的,而不是与关系类型真正相关的。 - Ahmed Alhallag
4
@AhmedAlhallag,确实,它们都是以相同的方式实现的,即“具有”。我从未想过说聚合总是使用集合。我会尝试提供另一个不使用集合的示例,以避免混淆。谢谢。 - Ahmad Abdelghany
@AhmadAbdelghany 感谢您的澄清和努力。 - Ahmed Alhallag
从实现的角度来看,这对我帮助最大。谢谢! - Rafs


32

正如其他人所说,关联是对象之间的关系,而聚合和组合是关联的类型。

从实现的角度来看,聚合是通过拥有一个引用类成员来实现的。例如,如果类A聚合了类B的一个对象,你会得到类似下面这样的代码(在C++中):

class A {
    B & element;
  // or B * element;
};
聚合的语义是,当一个对象 A 被销毁时,它所存储的 B 对象仍将存在。使用组合时,你拥有更强的关系,通常通过按值存储成员实现:

聚合的语义是,当一个对象 A 被销毁时,它所存储的 B 对象仍将存在。使用组合时,你拥有更强的关系,通常通过按值存储成员实现:

class A {
    B element;
};

在这里,当A对象被销毁时,它包含的B对象也将被销毁。实现这一点最简单的方法是通过按值存储成员,但您也可以使用一些智能指针,或在析构函数中删除成员:

class A {
    std::auto_ptr<B> element;
};

class A {
    B * element;

    ~A() {
        delete B;
    }
};

重要的是,在组合关系中,容器对象拥有内含对象,而在聚合关系中,它引用它。


11
这应该是唯一被接受的答案。在C#和Java中,除了原始类型外,没有组合的概念...然而,你会看到这些语言的开发人员“解释”组合。组合意味着一个对象存在于另一个对象内部。但在Java和C#中,你甚至无法做到这一点,因为一切都在堆上,你只能保存指向它的指针,这实际上是聚合而不是组合。而C++提供了组合的功能。 - Everyone
1
经过一段时间的搜索,我认为这是关于这个主题最准确且唯一的答案。 - Ahmed Alhallag

22
很惊奇人们对于三种关系概念——关联、聚合和组合之间的区别存在如此大的混淆。请注意,术语聚合和组合在C++社区中已经被使用了一段时间,可能早于它们被定义为UML类图中关联的特殊情况。
主要问题是广泛而持续的误解(即使在专业软件开发人员中也存在),即组合的概念意味着整体和其部分之间存在生命周期依赖性,以至于部分不能没有整体存在,忽略了还存在着非共享部分的部分-整体关联的情况,其中部分可以从整体中分离出来,并且在整体被销毁时仍能存活。
就我所见,这种混淆有两个根源:
  1. 在C++社区中,术语“聚合”被用来表示一个类定义了一个属性,用于引用另一个独立类的对象(例如参见[1]),这是UML类图中“关联”的意义。术语“组合”用于定义为其对象定义组件对象的类,因此在复合对象销毁时,这些组件对象也将被销毁。

  2. 在UML类图中,"聚合"和"组合"都被定义为表示整体部分关系的特殊情况的关联。在它们的定义中,“聚合”和“组合”的区别基于一个事实,即它是否允许在两个或多个整体之间共享一部分。他们将“组合”定义为具有不可共享(排他)部分的关系,而“聚合”可以共享它们的部分。另外,他们说了类似以下的话:很多情况下,但并非所有情况,组合与其部分之间存在生命周期依赖性,使得部分不能在没有整体的情况下存在。

因此,虽然UML已经将"聚合"和"组合"这些术语放在了正确的上下文中(即整体部分关系),但他们并没有成功地以清晰明确的方式定义它们,捕捉开发人员的直觉。然而,这并不奇怪,因为这些关系可以具有如此多种不同的属性(和实现细节),而且开发人员对如何实现它们的意见并不一致。
另请参见下面列出的SO问题的我的扩展答案
而在C++社区中被认为定义对象之间"组合"关系的属性(这种信念仍然被广泛认可):即两个相关对象(复合对象和其组成部分)之间的运行时生命周期依赖性,实际上并不是"组合"的特征,因为我们也可以由于引用完整性而在其他类型的关联中具有这样的依赖性。
例如,在一个SO答案中提出了以下代码模式来表示"组合"。
final class Car {    
  private final Engine engine;

  Car(EngineSpecs specs) {
    engine = new Engine(specs);
  }

  void move() {
    engine.work();
  }
}

被调查者声称对于“组合”,没有其他类可以引用/知道该组件是其特征。然而,在所有可能的“组合”情况下,这显然并不是真实的。特别是在汽车发动机的情况下,汽车制造商可能需要借助另一个类来实现,以便联系汽车所有者在出现问题时引用发动机。[1] http://www.learncpp.com/cpp-tutorial/103-aggregation/ 附录-关于组合与聚合的一些常见问题的不完整列表(StackOverflow)
[2009年4月]
聚合与组合的区别 [由于主要基于个人意见而被关闭]
[2009年4月]
组合和关联关系之间的区别是什么?
[2009年5月]
关联、聚合和组合之间的区别
[2009年5月]
组成和聚合之间的区别是什么? [重复]
[2009年10月]
聚合、组合和依赖之间的区别是什么? [标记为重复]
[2010年11月]
关联与聚合 [标记为重复]
[2012年8月]
Java中聚合与组合的实现差异
[2015年2月]
UML - 关联或聚合(简单代码片段)

点赞这个不完整的常见问题列表。 - Jacco

14

关联(Association)

关联表示两个类之间的关系。它可以是单向的(单方向)或双向的(双方向)

例如:

  1. 单向

客户下订单

  1. 双向

A与B结婚

B与A结婚

聚合(Aggregation)

聚合是一种关联,但具有特定的特点。聚合是一个较大的“整体”类包含一个或多个较小的“部分”类之间的关系。相反,较小的“部分”类是“整体”较大类的一部分。

例如:

俱乐部有会员

一个俱乐部(“整体”)由几个会员(“部分”)组成。会员可以在俱乐部外生活。如果俱乐部(“整体”)消失了,会员(“部分”)不会随之消失。因为会员可以属于多个俱乐部(“整体”)。

组合(Composition)

这是一种更强的聚合形式。“整体”负责创建或销毁其“部分”

例如:

学校有系

在这种情况下,如果学校(“整体”)消失了,系(“部分”)也将随之消失。 因为每个部分只能属于一个“整体”。


在聚合的情况下,我应该使用 class Club(){ _member = new Member } 还是将其作为引用传递 class Club(){ addMember(Member member) { this._member = member } } - Rolly
有趣。但是我对你的单向与双向概念感到困惑。例如,在处理订单时,需要与客户关联以查找要在标签上打印的名称,反之亦然,在处理客户时,则需要了解订单情况。难道不是同样的关系在两个方向上使用吗? - Christophe

10
重要的是要理解为什么我们应该使用超过一个关系线。最明显的原因是描述类之间的父子关系(当父删除时,所有子都会被删除),但更重要的是,我们想区分简单的关联和组合,以便对相关类的可见性和变化传播施加隐含的限制,这在理解和减少系统复杂性方面起着重要作用。
关联
描述类之间静态关系最抽象的方法是使用关联链接,它简单地说明两个或多个类之间存在某种链接或依赖关系。
弱关联
ClassA可能与ClassB相连,以显示其方法之一包括ClassB实例的参数,或返回ClassB实例。
强关联
ClassA也可能与ClassB相连,以显示它持有对ClassB实例的引用。
聚合(共享关联)
在ClassA(整体)和ClassB(部分)之间存在部分关系的情况下,我们可以更具体地使用聚合链接而不是关联链接,强调ClassB也可以被应用程序中的其他类聚合(因此聚合也称为共享关联)。

enter image description here

请注意,聚合链接并不表示ClassA拥有ClassB或两者之间存在父子关系(当父级删除时,所有子项都会被删除)。事实上,恰恰相反!聚合链接通常用于强调ClassA不是ClassB的唯一容器,因为事实上ClassB还有另一个容器。
聚合与关联: 在任何情况下,关联链接都可以替代聚合链接,而聚合无法替代仅存在“弱链接”情况下的关联,即ClassA具有包含ClassB参数的方法,但ClassA不持有对ClassB实例的引用。
Martin Fowler建议根本不使用聚合链接,因为它没有附加值,并且破坏了一致性,引用Jim Rumbaugh的话来说:“把它看作是一种建模安慰剂”。
组成(非共享关联): 我们应该更具体地使用组成链接,在ClassA和ClassB之间存在部分关系的情况下,在两者之间存在强生命周期依赖关系的情况下使用组成链接,这意味着当ClassA被删除时,ClassB也会被删除。

enter image description here

组合关系说明一个类(容器、整体)对其他类(部分)具有独占所有权,这意味着容器对象和其部分构成了父子关系。与关联和聚合不同,使用组合关系时,组成的类不能出现为复合类的返回类型或参数类型。因此,对组成的类的更改无法传播到系统的其余部分。因此,使用组合限制了系统随着增长而变得复杂。
测量系统复杂性
系统复杂性可以通过查看UML类图并评估关联、聚合和组合关系线来简单地衡量。衡量复杂性的方法是确定更改特定类可以影响多少个类。如果类A暴露了类B,则使用类A的任何给定类理论上都可能受到对类B的更改的影响。每个系统中潜在受影响类的数量之和就是总系统复杂度。
你可以在我的博客上阅读更多内容: http://aviadezra.blogspot.com/2009/05/uml-association-aggregation-composition.html

好的回答。1)关于组合示例的问题:Leng和Hand(组合)Person。如果我创建一个类Animal和Sleep,那么Sleep(聚合)Person;Sleep(聚合)Animal。这样正确吗? 2)Hand组合Person:class Person() { private hand = new Hand }。Sleep聚合Person:class Person() { private sleep = new Sleep }。在Sleep中使用关键字“new”是有效的吗?还是应该将其作为引用传递,因为它是聚合?class Person() { private Sleep _sleep; public addSleep(Sleep sleep) { this._sleep = sleep} } - Rolly
有趣。但是你所谓的弱关联在UML中不被称为关联,而被称为“使用依赖”。 - Christophe

10

关联、聚合、组合

关联聚合组合都涉及到具有的关系。

聚合组合关联的子集,更准确地描述了对象之间的关系。

聚合- 独立的关系。一个对象可以通过构造函数、方法、setter等传递和保存在类内部。

组合 - 依赖的关系。一个对象由拥有它的对象创建

*关联是替代子类型的一种选择


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接