继承表达“是(is-a)”的强契约关系,组合表达“有(has-a)”或“用(uses-a)”的松耦合协作;选择依据是设计意图而非便利性,需依判断流程确认关系本质。

继承和组合不是非此即彼的选择,而是服务于不同设计意图的工具。关键不在“用哪个”,而在“想表达什么关系”。
继承:表达“是(is-a)”的强契约关系
当子类在逻辑上**天然属于父类的一种**,且必须完全遵循父类的接口与行为契约时,才适合继承。比如 class Dog(Animal) —— 狗是一种动物,它理应能 .eat()、.sleep(),而且调用这些方法的行为语义必须与 Animal 一致。
注意几个实际约束:
- 父类需具备可扩展性:方法不应过度封闭(如被
final修饰),字段访问权限要合理(避免过度私有导致子类无法协作) - 子类不能破坏父类不变量:例如父类保证
self._age >= 0,子类重写方法时不能绕过该检查 - Liskov 替换原则(LSP)是红线:任何使用 Animal 的地方,换成 Dog 都不应出错或改变预期行为
组合:表达“有(has-a)”或“用(uses-a)”的松耦合协作
当你需要复用某类的能力,但二者之间**不存在本质的分类学归属关系**,就优先选组合。例如一个 Car 类需要导航能力,它不“是”一个 Navigator,而是“有一个”导航模块。
立即学习“Python免费学习笔记(深入)”;
典型写法:
class Car:
def __init__(self):
self.navigator = Navigator() # 组合:持有实例
<pre class="brush:php;toolbar:false;">def go_to(self, dest):
return self.navigator.calculate_route(dest)好处明显:
- 运行时可替换依赖(如切换为
OnlineNavigator或OfflineNavigator) - 避免继承带来的脆弱基类问题(父类一改,多个子类连锁崩溃)
- 天然支持单一职责:Car 聚焦行驶逻辑,Navigator 专注路径规划
别让“方便”掩盖设计意图
有时为了少写几行代码而用继承,实则是埋雷。例如:
- 只为复用几个工具方法就继承
BaseUtils?→ 应该组合或直接导入函数 - 因为两个类都有
.save()方法就让它们共用父类?→ 可能只是巧合,用协议(Protocol)或抽象基类(ABC)定义接口更准确 - 想“多继承”实现混入(mixin)?→ 检查是否真需要多重 is-a 关系,多数场景用组合 + 策略模式更清晰
一个实用判断流程
面对两个类 A 和 B,不确定用继承还是组合?按顺序问自己:
- B 在业务概念上是不是一种 A?(不是“能用 A 的功能”,而是“就是 A 的一种”)
- A 的所有公开行为对 B 是否都自然成立、无需特殊解释?
- 是否需要把 B 当作 A 向外暴露(比如函数参数类型注解为 A,实际传入 B)?
- 如果答案都是“是”,再检查 A 是否设计为可继承(文档、方法开放性、是否有
__init__约束等)
只要有一条存疑,就转向组合——它更安全、更灵活、也更贴近真实世界的关系建模。










