概要
本文介绍面向对象编程中,实体对象之间的相互依赖关系,包括:关联、聚合、组成和继承关系。 虽然已经有很多文章,介绍这几种关系之间的区别,但仍有一些开发人员对这几种关系的理解存在困惑。 有时候,这几种依赖关系之间的界定,从代码结构上并不好区分,更多的是需要从语义的角度来理解。 笔者试图从面向对象的使用场景,来说明这些关系的区别。
面向对象的宗旨是用代码来描述真实世界的对象,这样我们的代码就能够达到更好的可读性和维护性。 既然真实世界的对象来自于具体的需求,我们就需要从需求出发,来抽象出对象模型。假定我们有以下需求:
- 有一家公司,雇佣了一位经理
- 经理每天来到公司,都需要使用通行证,刷卡才能进门
- 经理需要一群员工来一起完成项目
- 经理需要对项目负责,需要确保项目能够成功上线
- 项目是否成功,决定着经理的工资
以上是实际的需求描述,下面我们抽象出面向对象模型,然后说明各个对象的关系
1. 经理是公司的雇员
自然语言表达的是(is a)
,描述了继承关系Inheritance
, 用Java语言描述如下:
2. 经理使用通行证,刷卡才能进门
自然语言表达的使用(using)
,描述了关联关系(Association)
。关联关系是一种松散的关系,
关联的两个对象并存在谁属于谁,两者可以同时存在,生命周期也互不影响。没有通行证,经理只是进不了门,
但并不会消亡,他还是雇员,还是可以管理员工;同样,通行证也不因为经理离职了而丧失它的刷卡入门的功能。
经理需要使用通行证,构成了Manager对Passport的依赖; 使用通行证刷卡验证时,构成了Passport对Manager的依赖; 经理和通行证之间并没有从属关系,它们之间是相互利用的,这是一个双向的关联关系。
3. 经理下辖着一群员工
自然语言描述下辖
或者说拥有
,英文含义has
,描述了聚合关系(Aggregation)
。这群员工隶属于经理的管辖,
经理依赖于这群员工;同时,员工又是相对独立的,即便经理离职了,员工还是可以存在,员工不随着经理
的消亡而消亡。
然而,这和关联关系(Association)
有什么区别呢?如果经理下面只有一个员工,那不就是经理和员工之间
的单向关联吗?因为两者的生命周期互不影响啊,而且即便经理没有员工,经理也可以刷卡进门;即便员工没有经理,也可以照样干活啊。
没错,聚合关系(Aggregation)
就是一种关联关系(Association)
,从代码角度理解,两者结构是一致的。
但两者的语义逻辑不同,聚合关系体现的是整体包含局部的概念,在语义的角度,整体是离不开局部的,就像经理离开了员工就不能完成项目一样;关联关系体现的是平等的两个个体之间的关系,就像经理和通行证是两个平行的范畴。
4. 经理决定项目
经理和项目都是现实世界的实体,把他们抽象成对象,就可以发现:
-
经理和项目两个对象是一种双向关联关系,他们相互依赖对方。如果经理监管了多个项目,那么经理和多个项目之间也可以构成组合关系。
-
项目的生命周期是由经理的生命周期决定的,经理不在了,项目就进行不下去了。
从代码的角度,每次新建一个项目的时候,都需要与一个经理参与进来,先有的Manager对象,再有的Project对象。
5. 项目影响经理
最后一个需求,在逻辑上与上一个需要是紧密相关的,经理需对项目负责,决定这项目的成败,同时项目成败又影响着经理的工资。
这就构成了一种项目与经理之间的强耦合关系,组合关系(Composition)
描述的就是一种语义上紧密关联的关系。
总结
综合来看,聚合跟组合其实都属于关联,它们是两种特殊的关联。所以,抛开语义场景来看这几种依赖关系,并没有特别的区分。但结合实际的需求场景,考察各个对象之间的耦合紧密程度,就比较好理解了。面向对象编程中,之所以有这几种关系的区分,实际上也是对真实世界的映射,因为真实世界的实体之间,紧密程度也不一样,譬如以下几个例子:
-
一个家庭有很多孩子,那么,家庭和孩子这两个对象之间,就构成了聚合关系; 孩子有很多爱好,这些爱好会影响孩子的成长,决定孩子将来成为什么样的人,那么孩子和爱好,这两个对象之间,就构成了组合关系。
-
一群大雁在迁徙,雁群和大雁这两个对象之间,就构成了聚合关系;大雁都有翅膀,没有翅膀就不能飞翔,那么大雁和翅膀两个对象就构成了组合关系。
笔者总结了三个语义维度来区分对象之间的关联、聚合、组合关系:
类型 | 关联关系 | 聚合关系 | 组合关系 |
---|---|---|---|
主权对象 | 双方不存在谁主宰谁 | 其中一方主宰另一方 | 其中一方主宰另一方 |
生命周期 | 双方互不影响 | 双方互不影响 | 双方相互影响 |
从属对象 | 双方没有从属关系,相互独立 | 其中一方属于另一方属于主权对象 | 其中一方属于另一方 |
参考文献: