UML类图符号:关联,聚合和组成之间的区别


39

我对UML类图的某些表示感到困惑。

在此处输入图片说明

我很确定我知道协会的意思。两个类的实例之间的任何关系(一个类的实例需要了解第二个类的实例才能执行其工作)都属于关联关系。关联通常意味着类A具有对类B实例的引用(字段)。

但是,我很难理解“ 聚合”和“ 组合”箭头的含义。我的部分困惑是由于遇到这些符号的不同定义引起的。

聚合符号的两个定义:

定义1:每当类A的实例持有类B的实例的集合(例如,列表,数组等)时,两个类之间的聚合符号就适用。

定义2:如果类A的实例持有对类B的实例的引用,并且实例B的依赖于实例A的生命周期,则两个类之间的聚合链接是合适的含义:类A的实例被删除时,类B的实例也将被删除。类B的实例完全包含在类A的实例中,而不是类A的实例仅拥有对类A的引用的引用。 B类(常规协会)。

关于“组合”符号的含义以及它与“聚合”符号的区别,我不确定。

请澄清定义并帮助我理解。具体的例子将受到欢迎。


定义2听起来更像是“组合”而不是“聚合”的定义。定义1听起来很正确。
jbx 2015年

Answers:


32

关联,聚合和构成这三个链接构成了一种等级,用于衡量两个类别之间的关联程度。

在量表的一端,存在关联,两个类的对象可以相互了解,但是它们不会影响彼此的寿命。这些对象可以独立存在,并且哪个A类对象知道哪些B类对象可以随时间变化。

在量表的另一端,有成分。组合表示一个部分-整个关系,使得类B是类A的组成部分。如果没有类B的对象在逻辑上不能存在类A的对象,则通常使用此关系。

聚合关系介于两者之间,但似乎没人同意确切的位置,因此也没有关于聚合含义的普遍认可的定义。从这个意义上讲,您找到的两个定义都是正确的,如果您问10个人,则可能会冒11个不同定义的风险。


1
感谢您的回答。这是我的理解方式,请说这是否合理。1-关联是每当A对象需要了解B对象才能执行其功能时。2-聚合和组合都定义了“所有权”关系-类A的实例在概念上拥有类B的实例。但是B实例的生存期独立于A实例的生存期。例如,一个有员工的部门。该部门拥有“雇员”实例,但如果没有该部门,它将继续存在。合成就像聚合一样,但是
Aviv Cohn 2014年

1
B实例的生存期取决于A实例的生存期。更加牢固的“所有权”关系。例如:汽车和车轮。汽车“完全容纳”车轮。如果没有Car实例,则Wheel实例将不会继续存在。这是合理的区分吗?
Aviv Cohn 2014年

@Prog:是的,这是一个合理的定义。请记住,其他人可能不会共享该定义,您可能需要向他们解释对聚合的使用。
Bart van Ingen Schenau 2014年

您会说“聚合”符号最常见的定义是什么?我正在使用的定义?“具有集合”的定义?还有吗
Aviv Cohn 2014年

以下对OMG标准的参考具有指导意义。关联和组成非常简单。聚合是不稳定的。在实践中,我发现“部分测试”效果很好(“所有权”是考虑问题的次佳方式)。一个人可以成为俱乐部的一部分,因此俱乐部聚集了人们(它不拥有他们)。当俱乐部被摧毁时,人们继续存在。
Huliax

10

组成是object A包含的时间,object B并且object A还负责创建内容object B

构图关系

我们有一个A类,它将由B类使用。

final class A
{
}

构图看起来有多种选择。

直接初始化组成:

final class B
{
    private $a = new A();
}

构造函数初始化组成

final class B
{
    private $a;

    public function __construct()
    {
        $this->a = new A();
    }
}

延迟初始化组成

final class B
{
    private $a = null;

    public function useA()
    {
        if ($this->a === null) {
            $this->a = new A();
        }

        /* Use $this->a */
    }
}

您会看到这在类A和之间建立了紧密的关系BB没有,类就无法存在A。这严重违反了依赖注入原理,该原理说:

依赖项是可以使用的对象(服务)。注入是将依赖项传递给将使用它的依赖对象(客户端)。该服务已成为客户端状态的一部分。将服务传递给客户端,而不是允许客户端构建或找到服务,是该模式的基本要求。

有时组合很有意义,例如new DateTime用php或new std::vector<int>C ++进行调用。但是通常,这是警告,您的代码设计是错误的。

在这种情况下,如果class A它将是用于缓存的特殊对象,class B则将始终使用的实现来缓存class A,而您将无法控制动态更改它,这很糟糕。

同样,如果您使用了惰性初始化组合,这意味着您将有一个工作object BuseA()方法(称为方法),并且创建object A将失败,则您object B突然变得无用。


另一方面,聚集是一种遵循DI原理的关系方式。object B需要使用object A,那么您应该将已经创建的实例传递object Aobject B,并且如果创建object A失败,则首先不会传递任何内容。

简而言之,聚合是依赖项注入原则的UML表示,无论是构造函数注入,setter注入还是公共属性注入。

这些都是汇总

最严格的构造函数注入(object B没有不能存在object A)。

final class B
{
    private $a;

    public function __construct(A $a)
    {
        $this->a = $a;
    }
}

松动(您可能会或可能不会object A在inside中使用它object B,但是如果这样做,则可能应该先设置它)。

通过设置器:

final class B
{
    private $a;

    public function setA(A $a)
    {
        $this->a = $a;
    }
}

通过公共财产:

final class B
{
    public $a;
}

如果使用的只是类的具体实现,则没有什么好方法可以证明使用聚合而不是使用组合,但是一旦您开始注入接口或使用C ++抽象类,则聚合将是唯一的方法。履行您的合同。


1
查看代码示例确实有帮助!没有代码的英语解释都显得如此模糊和主观。
Niko Bellic

1

另外,摘录了当前的UML标准:

11.5.4关联–语义–符号

[...]二进制关联的一端可能是aggregation = AggregationKind :: shared或aggregation = AggregationKind :: composite。当一端具有aggregation = AggregationKind :: shared时,在关联行的末端添加一个空心菱形作为终端装饰,该行的末端与标有aggregation = AggregationKind :: shared的一端相对。钻石应明显小于协会的钻石符号。一个具有aggregation = AggregationKind :: composite的关联在相应的末端同样具有一个菱形,但是在填充菱形方面有所不同。[…]

9.5.4分类–属性–符号

[…]有时,属性用于对情况进行建模,在这种情况下,一个实例用于将一组实例组合在一起;这称为聚合。为了表示这种情况,一个Property具有一个AggregationKind类型的聚合属性;代表整个群体的实例由财产的所有者分类,代表分组的个体的实例按财产的类型分类。AggregationKind是具有以下文字值的枚举:

  • none:指示该属性没有聚合语义。
  • 共享的:表示该属性具有共享的聚合语义。共享聚合的精确语义因应用程序区域和建模器而异。
  • Composite:表示该属性是复合聚合的,即,复合对象负责所组成对象的存在和存储(请参见11.2.3中各部分的定义)。复合聚合是聚合的一种强大形式,它要求一次将一个对象最多包含在一个复合对象中。如果删除复合对象,则随即删除作为对象的所有零件实例。

[…]


0

我已经在Stackoverflow上发布了答案

基本上,聚合比简单的关联要强,但是聚合的对象可以像简单的关联一样彼此之间保持“生存”状态。

组合甚至比聚合更强,因为聚合的类不能被其他类聚合。它的“寿命”取决于容器。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.