生长中技术笔记
用 IS A 和 HAS A 区分继承与组合
问题背景
写面向对象代码时,经常遇到一个纠结:两个类之间的关系,到底该用继承还是用组合?把属性硬塞进父类,或者把本该继承的关系写成组合,都会让代码结构变得别扭。一个简单实用的判断方法是问自己两个问题:
- 子类 is a 父类吗?
- 对象 has a 某个东西吗?
IS A:继承关系
"IS A" 对应的是继承(inheritance)。如果 B 是 A 的一种,那 B 应该继承 A。
举个例子:
- 父类:
Animal(动物) - 子类:
Bird(鸟)、Dog(狗)、Fish(鱼)
判断方式很直接:
- 鸟 is a 动物 →
Bird继承Animal - 狗 is a 动物 →
Dog继承Animal - 鱼 is a 动物 →
Fish继承Animal
满足 IS A 关系时,子类可以复用父类的公共行为和状态,同时扩展自己的特性。
HAS A:组合与属性
"HAS A" 对应的是组合(composition)或简单的属性持有。如果对象 B 拥有对象 A 作为其一部分,那 B has a A。
继续上面的例子:
- 鸟 has a 翅膀 →
Bird持有一个Wing属性 - 狗 has a 尾巴 →
Dog持有一个Tail属性 - 鱼 has a 鳃 →
Fish持有一个Gill属性
满足 HAS A 关系时,通常用成员变量(组合)来表达,而不是让"翅膀"去继承"鸟"。
快速判断流程
拿到两个类 A 和 B,按下面的顺序问:
- B is a A 吗? 如果语义上成立("B 是 A 的一种"),优先考虑继承。
- B has a A 吗? 如果语义上成立("B 拥有 A"),用组合或属性。
- 如果两个都不自然,可能两者之间没有直接关系,或者需要引入第三个抽象。
边界与注意事项
- 不是所有"是"都等于继承。 比如"正方形 is a 矩形"在数学上成立,但在代码里强行继承会导致
setWidth和setHeight互相干扰(违反 Liskov 替换原则)。遇到这种情况,组合或独立建模往往更合适。 - HAS A 不一定是简单字段。 如果"鸟 has a 翅膀"中的翅膀本身有复杂行为(扇动、折叠),那
Wing应该是一个独立类,Bird通过组合持有它,而不是只存一个int wingSize。 - 多重 HAS A 很常见。 一个对象可以同时 has a 多个东西,不受某些语言中单继承的限制。
- 语言无关。 上面的判断方法不依赖具体语言。无论是 C++ 的继承、Java 的
extends还是 Python 的class B(A),语法不同,语义判断方式一致。
小结
IS A 指向继承,HAS A 指向组合或属性。写代码前花几秒钟用这两个问句过一遍,能避免大量"该继承却组合了"或"该组合却继承了"的结构问题。它不是万能规则,但作为第一直觉非常实用。
<!-- csdn-article-id: 129330779 -->本文最初于 2023/3/4 发布在 CSDN。
评论
正在读取评论…