继承是建模is-a关系的严谨工具,仅当子类确实是父类的具体类型时才适用,需满足自然语义、传递性、行为兼容性,且不可逆或伪is-a。

继承不是代码复用的“快捷键”,而是建模现实关系的严谨工具。只有当子类**真的是父类的一种具体类型**时,才构成 IS-A 关系,才能用 extends(Java)或 public inheritance(C++)。
IS-A 的核心判断法:能自然说出“X 是一种 Y”
这句话必须符合日常语义和逻辑常识,不能靠强行解释成立。
- ✅ 图书是一种商品 →
BookProduct extends Product - ✅ 狗是一种动物 →
Dog extends Animal - ✅ 短尾猫是一种猫 →
ManxCat extends Cat - ❌ 汽车有一种发动机 → 这是“has-a”,该用组合,不是继承
- ❌ 用户有一个订单 → 同样是“has-a”,应通过字段引用,而非继承
IS-A 具有传递性,但不可逆
如果 B is-a A,C is-a B,那 C is-a A —— 这是合理的层级推导。但反过来不成立。
- 学生 is-a 人,本科生 is-a 学生 → 所以本科生 is-a 人 ✅
- 但人 is-a 学生 ❌(显然不是所有“人”都是“学生”)
- 所以父类不能反向继承子类,也不能把通用类硬塞进具体类里
警惕伪 IS-A:别被“共用字段/方法”带偏
两个类有相同属性或行为,不等于它们是 IS-A 关系。复用 ≠ 继承。
- 比如“订单”和“退货单”都有编号、时间、金额 → 它们可能都属于“交易单”,可抽象出
TradeDocument父类 ✅ - 但“用户”和“商品”也都有 ID 和名称 → 不能因此让
User extends Product❌(用户不是一种商品) - 此时更合适的方式是提取公共字段到工具类、用接口定义行为契约,或采用组合
IS-A 还意味着行为兼容性
子类对象必须能在任何期望父类对象的地方安全替换(Liskov 替换原则)。这不仅是语法通过,更是语义合理。
- ✅
Dog能替代Animal调用eat(),没问题 - ⚠️ 若
Ostrich extends Bird,但重写了fly()抛异常或直接 return → 就破坏了“鸟会飞”的隐含契约,这不是真正的 IS-A - 这时应反思:鸵鸟真是“鸟”这个行为模型下的合适子类?还是该把“飞行能力”拆成接口(
Flyable)更准确?











