摘自《Java面向对象编程》一书
在建立对象模型时,很容易把依赖、关联和聚集关系混淆。当对象A和对象B之间存在依赖、关联或聚集关系时,对象A都有可能调用对象B的方法,这是三种关系之间的相同之处,除此之外,它们有着不同的特征。
关联(Association)关系是类与类之间的联接,它使一个类知道另一个类的属性和方法。关联可以是双向的,也可以是单向的。在Java语言里,关联关系是使用实例变量实现的。
聚合(Aggregation)关系是关联关系的一种,是强的关联关系。聚合是整体和个体之间的关系。例如,汽车类与引擎类、轮胎类,以及其它的零件类之间的关系便整体和个体的关系。与关联关系一种,聚合关系也是通过实例变量实现的。但是关联关系所涉及的两个类是处在同一层次上的,而在聚合关系中,两个类是处在不平等层次上的,一个代表整体,另一个代表部分。
合成(Composition)关系是关联关系的一种,是比聚合关系强的关系。它要求普通的聚合关系中代表整体的对象负责代表部分对象的生命周期,合成关系是不能共享的。代表整体的对象需要负责保持部分对象和存活,在一些情况下将负责代表部分的对象湮灭掉。代表整体的对象可以将代表部分的对象传递给另一个对象,由后者负责此对象的生命周期。换言之,代表部分的对象在每一个时刻只能与一个对象发生合成关系,由后者排他地负责生命周期。部分和整体的生命周期一样。
依赖(Dependency)也是类与类之间的连接,依赖总是单向的。依赖关系表示一个类依赖于另一个类的定义。例如,一个人(Person)可以买车(car)和房子(House),Person类依赖于Car类和House类的定义,因为Person类引用了Car和House。与关联不同的是,Person类里并没有Car和House类型的属性,Car和House的实例是以参量的方式传入到buy()方法中去的。一般而言,依赖关系在Java语言中体现为局域变量、方法的参量,以及对静态方法的调用。每一个依赖关系都有一个名字。
1.依赖关系的特征
对于两个相对独立的系统,当一个系统负责构造另一个系统的实例,或者依赖另一个系统的服务时,这两个系统之间主要体现为依赖关系,例如生产零件的机器和零件,机器负责构造零件对象。再例如充电电池和充电器,充电电池通过充电器来充电。再例如自行车Bicycle和打气筒Pump,自行车通过打气筒来充气。图1-39为Bicycle类与Pump类的类框图。
图1-39 Bicycle类与Pump类的依赖关系
Bicycle类和Pump类之间是依赖关系,在Bicycle类中无需定义Pump类型的变量。Bicycle类的定义如下:
public class Bicycle{
/** 给轮胎充气 */
public void expand(Pump pump){
pump.blow();
}
}
在现时生活中,通常不会为某一辆自行车配备专门的打气筒,而是在需要充气的时候,从附近某个修车棚里借个打气筒打气。在程序代码中,表现为Bicycle类的expand()方法有个Pump类型的参数。以下程序代码表示某辆自行车先后到两个修车棚里充气:
myBicycle.expand(pumpFromRepairShed1); //到第一个修车棚里充气
myBicycle.expand(pumpFromRepairShed2); //若干天后,到第二个修车棚里充气
2.关联关系的特征
对于两个相对独立的系统,当一个系统的实例与另一个系统的一些特定实例存在固定的对应关系时,这两个系统之间为关联关系。例如客户和订单,每个订单对应特定的客户,每个客户对应一些特定的订单;再例如公司和员工,每个公司对应一些特定的员工,每个员工对应一特定的公司;再例如自行车和主人,每辆自行车属于特定的主人,每个主人有特定的自行车,图1-40显示了主人和自行车的关联关系。而充电电池和充电器之间就不存在固定的对应关系,同样自行车和打气筒之间也不存在固定的对应关系。
图1-40 主人和自行车的关联关系
Person类与Bicycle类之间存在关联关系,这意味着在Person类中需要定义一个Bicycle类型的成员变量。以下是Person类的定义:
public class Person{
private Bicycle bicycle; //主人的自行车
public Bicycle getBicycle(){
return bicycle;
}
public void setBicycle(Bicycle bicycle){
this.bicycle=bicycle;
}
/** 骑自行车去上班 */
public void goToWork(){
bicycle.run();
}
}
在现时生活中,当你骑自行车去上班时,只要从家里推出自己的自行车就能上路了,不象给自行车打气那样,在需要打气时,还要四处去找修车棚。因此,在Person类的goToWork()方法中,调用自身的bicycle对象的run()方法。假如goToWork()方法采用以下的定义方式:
/** 骑自行车去上班 */
public void goToWork(Bicycle bicycle){
bicycle.run();
}
那就好比去上班前,还要先四处去借一辆自行车,然后才能去上班。
3.聚集关系的特征
当系统A被加入到系统B中,成为系统B的组成部分时,系统B和系统A之间为聚集关系。例如自行车和它的响铃、龙头、轮胎、钢圈以及刹车装置就是聚集关系,因为响铃是自行车的组成部分。而人和自行车不是聚集关系,因为人不是由自行车组成的,如果一定要研究人的组成,那么他应该由头、躯干和四肢等组成。由此可见,可以根据语义来区分关联关系和聚集关系。
聚集关系和关联关系的区别还表现在以下方面:
(1) 对于具有关联关系的两个对象,多数情况下,两者有独立的生命周期。比如自行车和他的主人,当自行车不存在了,它的主人依然存在;反之亦然。但在个别情况下,一方会制约另一方的生命周期。比如客户和订单,当客户不存在,它的订单也就失去存在的意义。
(2) 对于具有聚集关系(尤其是强聚集关系)的两个对象,整体对象会制约它的组成对象的生命周期。部分类的对象不能单独存在,它的生命周期依赖于整体类的对象的生命周期,当整体消失,部分也就随之消失。比如小王的自行车被偷了,那么自行车的所有组件也不存在了,除非小王事先碰巧把一些可拆卸的组件(比如车铃和坐垫)拆了下来。
不过,在用程序代码来表示关联关系和聚集关系时,两者比较相似。图1-41为自行车Bicycle与响铃Bell的聚集关系。
图1-41 自行车和响铃的聚集关系
以下例程1-6是Bicycle类的源程序。
例程1-6 Bicycle.java
public class Bicycle{
private Bell bell;
public Bell getBell(){
return bell;
}
public void setBell(Bell bell){
this.bell=bell;
}
/** 发出铃声 */
public void alert(){
bell.ring();
}
}
在Bicycle类中定义了Bell类型的成员变量,Bicycle类利用自身的bell成员变量来发出铃声,这和在Person类中定义了Bicycle类型的成员变量,Person类利用自身的bicycle成员变量去上班很相似。