在面向对象的程序设计中,理清楚类和类之间的关系,画出清晰的类图,有助于能极大地提高程序开发的效率。
类和类之间的关系主要有继承(inheritance)、实现(realization)、依赖(dependency)、关联(association)、聚合(aggregation)和组合(composition)。下面用PlantUML画各种关系的类图,并分别介绍。
1、继承(inheritance)和实现(realization)
继承和实现可以放在一起来了解,区别就在于上层是父类还是一个接口。
对应的PlantUML代码如下:
@startuml
Title "继承和实现"
Car <|-- Ferrari
note as N1
Ferrari类继承Car类
end note
InterI <|.. ClassC
note as N2
类ClassC实现了InterI接口
end note
@enduml
2、依赖关系(dependency)
依赖就是一个类A使用到了另一个类B,所以,有些英文的材料中将这种关系表述为引用(using)。这种关系具有偶然性、临时性,也非常弱,但是,类B的变化会影响到类A。代码中,一般表现为,类A中的某个成员函数用到了某个类B类型的参数,或者类A中的某个成员函数,用到了类B类型的某个临时变量,等等。
在UML中,依赖关系用带箭头的虚线表示,箭头从使用类指向被依赖的类。如下:
@startuml
Title "依赖"
note as N1
Person类的eat方法中
用到了Food类,
Person类依赖于Food类
end note
class Person {
+ void eat(Food f);
}
Person ..> Food: using
@enduml
3、关联关系(Association)
关联体现的是两个类之间语义级别的一种强依赖关系,是一种对象之间的引用关系,比如商品类和订单类。这种关系比依赖关系要密切很多,不偶然,也不临时。一般地,程序实现中,类A中的某个属性的类型是类B,我们称类A和类B的关系是关联关系,其中类A称为关联类,类B称为被关联类。关联可以是单向、双向的。
在UML中,关联关系用由关联类A指向被关联类B的带箭头实线表示,在关联的两端可以标注关联双方的多重性标记。如下:
@startuml
Title "关联"
note as N1
单个订单可包含多个商品
end note
class Order {
- List<Product> proList;
}
Order "1"-->"n" Product
@enduml
4、聚合关系(Aggregation)
聚合关系是关联关系的一种特例,聚合关系表示的是一种整体和部分的关系,has-a。比如,Family和Child,Child是Family的一部分,再比如Company和Staff。聚合关系在程序中的体现和上面的关联关系是一样的,只能在语义层面进行区分。可以看出,聚合时一种比一般关联关系更强的关系,需要有整体和部分的联系。
@startuml
Title "聚合"
note as N1
一个家庭可能
有0..n个孩子
end note
class Family {
- List<Child> children;
}
Family "1" o-- "0..n" Child:Contains
@enduml
5、组合(composition)
组合关系亦是一种特殊的关联关系,和上面的聚合关系一样,也表示一种整体和部分的关系。不同于聚合组合关系中的部分,离开整体没有意义,只能依附于整体存在。比如,汽车和汽车的引擎,引擎是汽车的一部分,离开汽车,引擎在当前的建模语境中没有意义。可以看出,组合是比聚合更强的关联关系。程序实现中,组合关系和一般的关联关系也没有区别,只能从语义层面区分。
在UML中,组合关系以实心菱形加实线箭头表示。
@startuml
Title "组合"
note as N1
一辆车有且
只有一个引擎
引擎离开车没有意义
end note
class Car {
- Engine e;
}
Car "1" *-- "1" Engine:Has
@enduml
5、总结
上面提到的集中关联关系中,继承和实现是纵向的。其它的集中关系,都是类之间横向的关系。关联、聚合和组合在程序实现上,没有区分,只是语义上的差别。然而,通过这几种关系能帮助我们在设计类时能够理顺思路,实现上少走弯路。