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.