UML关联、聚合、组合关系的区别


请尊重原创版权,转载注明出处。

概要

本文介绍面向对象编程中,实体对象之间的相互依赖关系,包括:关联、聚合、组成和继承关系。 虽然已经有很多文章,介绍这几种关系之间的区别,但仍有一些开发人员对这几种关系的理解存在困惑。 有时候,这几种依赖关系之间的界定,从代码结构上并不好区分,更多的是需要从语义的角度来理解。 笔者试图从面向对象的使用场景,来说明这些关系的区别。

面向对象的宗旨是用代码来描述真实世界的对象,这样我们的代码就能够达到更好的可读性和维护性。 既然真实世界的对象来自于具体的需求,我们就需要从需求出发,来抽象出对象模型。假定我们有以下需求:

  1. 有一家公司,雇佣了一位经理
  2. 经理每天来到公司,都需要使用通行证,刷卡才能进门
  3. 经理需要一群员工来一起完成项目
  4. 经理需要对项目负责,需要确保项目能够成功上线
  5. 项目是否成功,决定着经理的工资

以上是实际的需求描述,下面我们抽象出面向对象模型,然后说明各个对象的关系

1. 经理是公司的雇员

自然语言表达的是(is a),描述了继承关系Inheritance, 用Java语言描述如下:

class Employee { ... }

class Manager extends Employee { ... }

2. 经理使用通行证,刷卡才能进门

自然语言表达的使用(using),描述了关联关系(Association)。关联关系是一种松散的关系, 关联的两个对象并存在谁属于谁,两者可以同时存在,生命周期也互不影响。没有通行证,经理只是进不了门, 但并不会消亡,他还是雇员,还是可以管理员工;同样,通行证也不因为经理离职了而丧失它的刷卡入门的功能。

class Manager extends Employee {    
    void login(Passport passport) {         <----+
        passport.swipe(this)                     |
    }                                            |
}                                                |
                                                 |
class Passport {                                 |
    void swipe(Manager manager) {           <----+
        // Check Identity of the manager
    }
}

经理需要使用通行证,构成了Manager对Passport的依赖; 使用通行证刷卡验证时,构成了Passport对Manager的依赖; 经理和通行证之间并没有从属关系,它们之间是相互利用的,这是一个双向的关联关系。

3. 经理下辖着一群员工

自然语言描述下辖或者说拥有,英文含义has,描述了聚合关系(Aggregation)。这群员工隶属于经理的管辖, 经理依赖于这群员工;同时,员工又是相对独立的,即便经理离职了,员工还是可以存在,员工不随着经理 的消亡而消亡。

class Manager extends Employee {
    List<Worker> workers = new ArrayList<Worker>();

    void add(Worker worker) {       <>------+
        workers.add(worker)                 |
    }                                       |
}                                           |
                                            |
class Worker extends Employee { }    <------+

然而,这和关联关系(Association)有什么区别呢?如果经理下面只有一个员工,那不就是经理和员工之间 的单向关联吗?因为两者的生命周期互不影响啊,而且即便经理没有员工,经理也可以刷卡进门;即便员工没有经理,也可以照样干活啊。

没错,聚合关系(Aggregation)就是一种关联关系(Association),从代码角度理解,两者结构是一致的。 但两者的语义逻辑不同,聚合关系体现的是整体包含局部的概念,在语义的角度,整体是离不开局部的,就像经理离开了员工就不能完成项目一样;关联关系体现的是平等的两个个体之间的关系,就像经理和通行证是两个平行的范畴。

4. 经理决定项目

经理和项目都是现实世界的实体,把他们抽象成对象,就可以发现:

  • 经理和项目两个对象是一种双向关联关系,他们相互依赖对方。如果经理监管了多个项目,那么经理和多个项目之间也可以构成组合关系。

  • 项目的生命周期是由经理的生命周期决定的,经理不在了,项目就进行不下去了。

从代码的角度,每次新建一个项目的时候,都需要与一个经理参与进来,先有的Manager对象,再有的Project对象。

class Manager extends Employee {       <------+
    void setSalary() { ... }   ---+           |
}                                 |           |
                                  |           |
class Project {                   |           |
    Manager mManager;             |           |
                                  |           |
    Project(Manager manager) {    |    -------+
        mManager = manager;       |
    }                             |
                                  |
    boolean isSuccess() { }   <---+
}

5. 项目影响经理

最后一个需求,在逻辑上与上一个需要是紧密相关的,经理需对项目负责,决定这项目的成败,同时项目成败又影响着经理的工资。

这就构成了一种项目与经理之间的强耦合关系,组合关系(Composition)描述的就是一种语义上紧密关联的关系。

总结

综合来看,聚合跟组合其实都属于关联,它们是两种特殊的关联。所以,抛开语义场景来看这几种依赖关系,并没有特别的区分。但结合实际的需求场景,考察各个对象之间的耦合紧密程度,就比较好理解了。面向对象编程中,之所以有这几种关系的区分,实际上也是对真实世界的映射,因为真实世界的实体之间,紧密程度也不一样,譬如以下几个例子:

  • 一个家庭有很多孩子,那么,家庭和孩子这两个对象之间,就构成了聚合关系; 孩子有很多爱好,这些爱好会影响孩子的成长,决定孩子将来成为什么样的人,那么孩子和爱好,这两个对象之间,就构成了组合关系。

  • 一群大雁在迁徙,雁群和大雁这两个对象之间,就构成了聚合关系;大雁都有翅膀,没有翅膀就不能飞翔,那么大雁和翅膀两个对象就构成了组合关系。

笔者总结了三个语义维度来区分对象之间的关联、聚合、组合关系:

类型 关联关系 聚合关系 组合关系
主权对象 双方不存在谁主宰谁 其中一方主宰另一方 其中一方主宰另一方
生命周期 双方互不影响 双方互不影响 双方相互影响
从属对象 双方没有从属关系,相互独立 其中一方属于另一方属于主权对象 其中一方属于另一方

参考文献