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

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个回答

8

组合(如果你移除“整体”,“部分”也会自动被移除--“拥有关系”)

  • 在新类内创建现有类的对象。这被称为组合,因为新类由现有类的对象组成。

  • 通常使用普通成员变量。

  • 如果组合类自动处理子类的创建/销毁责任,则可以使用指针值。

enter image description here

C++ 中的组合

#include <iostream>
using namespace std;
/********************** Engine Class ******************/
class Engine
{
    int nEngineNumber;
    public:
    Engine(int nEngineNo);
    ~Engine(void);
};
Engine::Engine(int nEngineNo)
{
    cout<<" Engine :: Constructor " <<endl;
}
Engine::~Engine(void)
{
    cout<<" Engine :: Destructor " <<endl;
}
/********************** Car Class ******************/
class Car
{
    int nCarColorNumber;
    int nCarModelNumber;
    Engine objEngine;
    public:
    Car (int, int,int);
    ~Car(void);
};
Car::Car(int nModelNo,int nColorNo, int nEngineNo):
nCarModelNumber(nModelNo),nCarColorNumber(nColorNo),objEngine(nEngineNo)
{
    cout<<" Car :: Constructor " <<endl;
}
Car::~Car(void)
{
    cout<<" Car :: Destructor " <<endl;
    Car
    Engine
    Figure 1 : Composition
}
/********************** Bus Class ******************/
class Bus
{
    int nBusColorNumber;
    int nBusModelNumber;
    Engine* ptrEngine;
    public:
    Bus(int,int,int);
    ~Bus(void);
};
Bus::Bus(int nModelNo,int nColorNo, int nEngineNo):
nBusModelNumber(nModelNo),nBusColorNumber(nColorNo)
{
    ptrEngine = new Engine(nEngineNo);
    cout<<" Bus :: Constructor " <<endl;
}
Bus::~Bus(void)
{
    cout<<" Bus :: Destructor " <<endl;
    delete ptrEngine;
}
/********************** Main Function ******************/
int main()
{
    freopen ("InstallationDump.Log", "w", stdout);
    cout<<"--------------- Start Of Program --------------------"<<endl;
    // Composition using simple Engine in a car object
    {
        cout<<"------------- Inside Car Block ------------------"<<endl;
        Car objCar (1, 2,3);
    }
    cout<<"------------- Out of Car Block ------------------"<<endl;
    // Composition using pointer of Engine in a Bus object
    {
        cout<<"------------- Inside Bus Block ------------------"<<endl;
        Bus objBus(11, 22,33);
    }
    cout<<"------------- Out of Bus Block ------------------"<<endl;
    cout<<"--------------- End Of Program --------------------"<<endl;
    fclose (stdout);
}

输出

--------------- Start Of Program --------------------
------------- Inside Car Block ------------------
Engine :: Constructor
Car :: Constructor
Car :: Destructor
Engine :: Destructor
------------- Out of Car Block ------------------
------------- Inside Bus Block ------------------
Engine :: Constructor
Bus :: Constructor
Bus :: Destructor
Engine :: Destructor
------------- Out of Bus Block ------------------
--------------- End Of Program --------------------

聚合(如果去掉“整个”,“部分”仍然存在-“没有所有权”)

  • 聚合是一种特定类型的组合,其中复杂对象和子对象之间没有所有权关系。当聚合被销毁时,子对象不会被销毁。

  • 通常使用指向生存于聚合类范围外的对象的指针变量/引用变量

  • 可以使用指向生存于聚合类范围外的对象的引用值

  • 不负责创建/销毁子类

enter image description here

C++中的聚合代码

#include <iostream>
#include <string>
using namespace std;
/********************** Teacher Class ******************/
class Teacher
{
    private:
    string m_strName;
    public:
    Teacher(string strName);
    ~Teacher(void);
    string GetName();
};
Teacher::Teacher(string strName) : m_strName(strName)
{
    cout<<" Teacher :: Constructor --- Teacher Name :: "<<m_strName<<endl;
}
Teacher::~Teacher(void)
{
    cout<<" Teacher :: Destructor --- Teacher Name :: "<<m_strName<<endl;
}
string Teacher::GetName()
{
    return m_strName;
}
/********************** Department Class ******************/
class Department
{
    private:
    Teacher *m_pcTeacher;
    Teacher& m_refTeacher;
    public:
    Department(Teacher *pcTeacher, Teacher& objTeacher);
    ~Department(void);
};
Department::Department(Teacher *pcTeacher, Teacher& objTeacher)
: m_pcTeacher(pcTeacher), m_refTeacher(objTeacher)
{
    cout<<" Department :: Constructor " <<endl;
}
Department::~Department(void)
{
    cout<<" Department :: Destructor " <<endl;
}
/********************** Main Function ******************/
int main()
{
    freopen ("InstallationDump.Log", "w", stdout);
    cout<<"--------------- Start Of Program --------------------"<<endl;
    {
        // Create a teacher outside the scope of the Department
        Teacher objTeacher("Reference Teacher");
        Teacher *pTeacher = new Teacher("Pointer Teacher"); // create a teacher
        {
            cout<<"------------- Inside Block ------------------"<<endl;
            // Create a department and use the constructor parameter to pass the teacher to it.
            Department cDept(pTeacher,objTeacher);
            Department
            Teacher
            Figure 2: Aggregation
        } // cDept goes out of scope here and is destroyed
        cout<<"------------- Out of Block ------------------"<<endl;
        // pTeacher still exists here because cDept did not destroy it
        delete pTeacher;
    }
    cout<<"--------------- End Of Program --------------------"<<endl;
    fclose (stdout);
}

输出
--------------- Start Of Program --------------------
Teacher :: Constructor --- Teacher Name :: Reference Teacher
Teacher :: Constructor --- Teacher Name :: Pointer Teacher
------------- Inside Block ------------------
Department :: Constructor
Department :: Destructor
------------- Out of Block ------------------
Teacher :: Destructor --- Teacher Name :: Pointer Teacher
Teacher :: Destructor --- Teacher Name :: Reference Teacher
--------------- End Of Program --------------------

谁曾经给这个答案点了反对?能否解释一下为什么要这么做? - Saurabh Raoot
真正让我困惑的是,在很多情况下,拥有者并不持有物品,而是它所拥有的“持有”了拥有者。例如,汽车没有引擎*类型指针,但引擎类有一个汽车类型成员来存储拥有它的汽车。我对此不太理解,特别是在这种情况下类之间的UML关系。 - dudu

6
这些答案的问题在于它们只讲了一半:它们解释了聚合和组合是关联的形式,但没有说一个关联是否可能既不是聚合也不是组合。
根据我对许多SO帖子和一些UML文档的简要阅读,我认为类关联有四种主要的具体形式:
1.组合:A由B组成;B没有A就不存在,就像房间在家里一样。 2.聚合:A有B;B可以存在而没有A,就像教室里的学生。 3.依赖:A使用B;A和B之间没有生命周期依赖,就像方法调用参数、返回值或在方法调用期间创建的临时对象。 4.泛化:A是B的一种。
当两个实体之间的关系不属于这些关系之一时,可以在通用意义上称之为“关联”,并以其他方式进一步描述(注意、构造等)。
我猜,“通用关联”主要用于两种情况:
1.当关系的具体细节仍在制定中时;这种关系在图表中应尽快转换为实际的关系(其中之一)。 2.当一个关系与UML预定义的4种关系都不匹配时;“通用”关联仍然提供了一种表示“不是其他关系之一”的关系的方法,这样你就不会使用错误的关系,并注明“这实际上不是聚合,只是UML没有其他可以使用的符号”。

1
如果所有其他选项都被排除,你将如何实现通用关联?如果 A 不由 B 组成(B 的值在 A 中),A 不是 B 的聚合(B 的引用不在 A 中),B 不是从 A 继承/实现的,也不是作为 A 的返回、参数或函数使用中使用的,那么你基本上没有任何关系。 - Dean P
1
@DeanP 目前可以使用通用的,以后再转换为其中之一(这样就可以实现);或者它可以是一个不符合这4个关系中任何一个的关系,比如你想要一个表示“看起来像”的关联,如果没有通用的关联,你将被迫使用其中之一,从而误导读者,而如果你使用通用的关联,你很可能会注释或加上一个说明,大多数人只有在不理解符号时才会阅读说明。;) - Oliver

5

https://www.linkedin.com/pulse/types-relationships-object-oriented-programming-oop-sarah-el-dawody/

组合(Composition):是一种“部分-整体”的关系。

例如,“发动机是汽车的一部分”、“心脏是身体的一部分”。

enter image description here

关联(Association):是一种“有一个”类型的关系。

例如,假设我们有两个类,则当这两个实体共享彼此的对象以进行某些工作时,并且在同一时间它们可以互不依赖地存在或者都具有自己的生命周期时,这两个类被称为“有一个”关系。

enter image description here

上面的示例显示了关联关系,因为Employee和Manager类都使用对方对象并且都有独立的生命周期。

聚合(Aggregation):基于“有一个”关系,是一种特殊形式的关联。

例如,“学生”和“地址”。每个学生都必须有一个地址,因此Student类和Address类之间的关系将是“有一个”类型的关系,但反过来则不成立。

enter image description here


在UML图中,关联可以是单向的,因此我无法理解您所举的关联和聚合之间的区别,它们看起来都是关联。地址不是学生的一部分,我认为聚合也定义了整体-部分关系,至少根据Bennetts书的说法是这样的。 - berimbolo

5
在一个非常简单的句子中:
聚合和组合是关联的子集。
  • A使用B->这是聚合。

  • A需要B->是组成。

在此处阅读更多信息


5
    Simple rules:
    A "owns" B = Composition : B has no meaning or purpose in the system 
    without A
    A "uses" B = Aggregation : B exists independently (conceptually) from A
    A "belongs/Have" B= Association; And B exists just have a relation
    Example 1:

    A Company is an aggregation of Employees.
    A Company is a composition of Accounts. When a Company ceases to do 
    business its Accounts cease to exist but its People continue to exist. 
    Employees have association relationship with each other.

    Example 2: (very simplified)
    A Text Editor owns a Buffer (composition). A Text Editor uses a File 
    (aggregation). When the Text Editor is closed,
    the Buffer is destroyed but the File itself is not destroyed.

4

关联(Association)是两个不同类之间的一种关系,关联可以是任何类型,例如一对一、一对多等。它将两个完全独立的实体连接在一起。

聚合(Aggregation)是关联的一种特殊形式,是一种单向的类(或实体)之间的关系,例如钱包(Wallet)和货币(Money)类。钱包有钱,但货币不一定需要有钱包,因此这是一种单向关系。在这种关系中,如果其中一个结束,另一个可以继续存在。在我们的例子中,如果没有钱包类,也并不意味着货币类不能存在。

组合(Composition)是聚合的一种受限形式,在这种形式中,两个实体(或类)高度依赖于彼此。例如,人类(Human)和心脏(Heart)。人需要心脏才能生存,心脏需要人体才能生存。换句话说,当类(实体)相互依赖且它们的寿命相同(如果一个死亡,则另一个也会死亡)时,这就是组合。如果没有人类类存在,则心脏类没有意义。


4

来源:Remo H. Jansen的书“Beginning React: Learning TypeScript 2.x - Second Edition”:

我们将那些对象具有独立生命周期但没有拥有关系的关系称为关联。让我们看一个老师和学生的例子。多个学生可以与单个教师相关联,而单个学生也可以与多个教师相关联,但两者都具有独立的生命周期(两者都可以独立创建和删除)。因此,当一个老师离开学校时,我们不需要删除任何学生,当一个学生离开学校时,我们也不需要删除任何老师。

我们将那些对象具有独立生命周期,但存在所有权并且子对象不能属于另一个父对象的关系称为聚合。让我们以手机和手机电池为例。一个电池可以属于一个电话,但如果该电话停止工作,并且我们从数据库中删除它,则电话电池不会被删除,因为它可能仍然可用。因此,在聚合中,虽然存在所有权,但对象具有其生命周期。

我们使用术语组成来指代对象没有独立生命周期的关系,如果父对象被删除,所有子对象也将被删除。让我们以问题和答案之间的关系为例。单个问题可以有多个答案,并且答案不能属于多个问题。如果我们删除问题,答案将自动被删除。


4
我认为这个链接可以帮助你完成作业:http://ootips.org/uml-hasa.html 为了理解这些术语,我记得在我早期的编程日子里有一个例子:
如果你有一个包含“盒子”对象的“棋盘”对象,那么这是组合关系,因为如果“棋盘”被删除,盒子也就没有存在的理由了。
如果你有一个“正方形”对象,它有一个“颜色”对象,当正方形被删除时,“颜色”对象可能仍然存在,这是聚合关系。
它们都是关联关系,主要区别在于概念上的不同。

4

组合(Composition): 这个概念是指,当你销毁一个对象(比如学校),与之绑定的另一个对象(教室)也会被一同销毁。两个对象不能独立存在。

聚合(Aggregation): 这个概念与上面(组合)相反,当你销毁一个对象(公司),与之绑定的另一个对象(员工)可以独立存在。

关联(Association): 组合和聚合是关联的两种形式。


1
严格来说,公司的员工不能没有公司存在。虽然不是说你杀了这些人,但他们也不再是该公司的员工了。因此,我认为更好的类比应该是分支机构和员工,即使分支机构关闭了,他们仍可能继续成为公司的员工。 - Alexander Popov
1
是的,绝对同意... +1 感谢 @AlexPopov 指出。 :) - Kulasangar

2
我想说明在Rails中如何实现这三个术语。ActiveRecord将两个模型之间的任何类型关系称为“关联”(association)。当阅读与ActiveRecord相关的文档或文章时,很少会发现“组合”(composition)和“聚合”(aggregation)这两个术语。通过在类的主体中添加关联类宏之一,可以创建一个关联。其中一些宏是“belongs_to”、“has_one”、“has_many”等。
如果我们想设置“组合”或“聚合”,我们需要将“belongs_to”添加到拥有的模型(也称为子模型),并将“has_one”或“has_many”添加到拥有的模型(也称为父模型)。设置“组合”或“聚合”取决于我们在子模型中传递给“belongs_to”调用的选项。在Rails 5之前,未经任何选项设置“belongs_to”会创建一个“聚合”,即子模型可以存在而没有父模型。如果我们想要“组合”,我们需要通过添加选项“required: true”来明确声明。
class Room < ActiveRecord::Base
  belongs_to :house, required: true
end

在Rails 5中,这个被改变了。现在,声明一个belongs_to关联默认创建一个composition,子对象不能存在没有父对象的情况。因此,上面的示例可以重写为:
class Room < ApplicationRecord
  belongs_to :house
end

如果我们希望子对象在没有父对象的情况下存在,我们需要通过选项optional来明确声明。

class Product < ApplicationRecord
  belongs_to :category, optional: true
end

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