has-a
has-a(英语:has_a、has a)是对象组合的关系[注 1],这是一种以组合为概念的关系[注 2]。
主要是用在数据库设计与面向对象程序设计以及面向对象的系统分析的程序设计领域,就是一个对象(部件/组合成分/成员)“属于”另一个对象(复合型态),而且是根据对象的所有权规则来执行动作。这个组合的关系也就是一个对象成为另一个对象的“一部分”,例如对象A成为对象B的一部分也就是对象B的成员。
简单来说,has-a关系对于对象来说就是对象的成员变量,或是成员对象。多个has-a关系会组成产生一个独有性阶层结构。
相关概念
分类性阶层(子类型)的is-a关系与has-a是相互对比的概念[注 3]。存在明确的is-a关系才较为适合使用继承方法。继承的问题在于各项对象紧密连接而难以修改,还有过度使用会导致混乱的阶层结构。以组合的概念来说,只要组合简单对象即可达成复杂的对象[1]。
不论是has-a或is-a关系,上层与下层对象两者大多的逻辑关系通常都不是很明确。对于逻辑关系的混乱需要元语言学专用词来做决定。标准模板库的容器是has-a关系的最佳范例。
以元语言学总结各种关系如下:
- 上位词—下位词(父型态—子型态)关系介于定义在分类性阶层结构的型态(类别)之间:
- 对于继承关系:对于上位词(父型态、父类别)而言,下位词(子型态、子类别)有型态(is-a)关系[注 4]。
- 整体词—分体词(完整体/实体/容器部件/成分/成员)关系介于定义在独有性阶层结构的型态(类别)之间:
- 概念—对象(型态—代币)关系介于型态(类别)与对象(实例)[注 7]:
- 对于型态(类别)而言,代币(对象)有实例关系[注 8]。
范例
ER模型
数据库的has-a关系通常以ER模型来表示。
如图所示,以大型多人在线角色扮演游戏为例,一个游戏账号可以建立多个游戏角色。这表示对于游戏角色而言[注 9],游戏账号有has-a关系[注 10]。
UML类别图
统一建模语言的类别图可以用来表示面向对象程序设计的has-a关系,也就是组合关系。
如图所示,具有菱形符号贴合的对象是由其他对象组成(或是包含其他对象):
- 组合关系:对于化油器而言汽车有has-a关系,或者汽车是由化油器组成的,汽车对象右方的黑色菱形符号表示组合关系。
- 聚合关系:池塘与鸭子两者用白色菱形符号来表示聚合关系,池塘包含鸭子但是无所有权,因为鸭子可以自行单独活动甚至主动离开池塘。
也就是说,组合关系表示具备所有权,聚合关系表示无所有权。
C++
试图以程序设计建模现实世界的事物时,区分组合与聚合两者的另一种方法是考虑成员对象(被包含的对象)的生命周期。
例如,用汽车来表示一个对象,汽车包含底盘这个对象,对于汽车的生命周期来说底盘的替换性很低,或是极不可能更换。因为底盘的生命周期相当于汽车本身的寿命,所以底盘与汽车是一种组合关系。另一方面,如果汽车包含轮胎对象,轮胎有可能耗损而需要更换。或者是汽车无法使用,但是轮胎堪用还能继续使用甚至回收分配给另一辆汽车。不管发生什么情形,轮胎的生命周期不同于汽车本身,所以轮胎与汽车是一种聚合关系。
用C++类别实作上述范例的关系:
- 组合关系:汽车要包含完整的底盘对象也就是把底盘宣告为成员变量。底盘对象只能在汽车类别的建构子来实例化而且成为汽车类别的成员变量之一(或者是定义为成员变量的资料型态再到建构子内设定属性), 底盘对象的生命周期会随着汽车对象被删除而终止。
- 聚合关系:汽车类别的成员变量通常是用指针来指向轮胎对象。轮胎对象可以在汽车对象的外部来实例化或是被删除,或是指定给其他不同的汽车对象。轮胎对象有独立的生命周期,汽车对象被删除时轮胎对象依旧还存在,轮胎可以从汽车分离出来。
也就是说,对于汽车类别而言,底盘对象是汽车的成员变量(组合关系),轮胎对象是汽车的指针变数(聚合关系)。
注释
参考文献
- ^ Cohen, Sheldon. 優先選用組合而非繼承(Favoring Composition Over Inheritance). Medium. 2023-05-19.