应优先使用组合而非继承。继承易导致基类修改引发连锁崩溃、mro黑盒依赖、职责污染等问题;组合通过行为注入(如flybehavior)实现灵活扩展、清晰职责与可测试性。

过度继承导致基类一改全崩
基类不是“公共仓库”,而是契约。一旦你在 Bird 里加了 fly(),所有子类(Penguin、Ostrich、Chicken)就自动继承这个行为——哪怕它们根本不会飞。你只能靠覆盖、抛异常、写注释来“否定”父类语义,这说明继承关系已经失真。真实业务中,这种“否定式继承”越多,基类就越像一个不断打补丁的逻辑黑洞。
- 每次修改
__init__参数,所有子类构造调用都得同步改 - 新增一个公共字段(比如
last_updated),就得检查几十个子类是否意外覆盖或忽略它 -
super().__init__()调用链一旦漏掉一层,初始化状态就残缺,问题常在运行时才暴露
多重继承让 MRO 变成黑盒依赖
当你写 class ReportExporter(ExcelMixin, PDFMixin, LoggingMixin):,看似组合能力,实则把行为耦合进了类定义。MRO 是确定的,但人脑记不住 C3 线性化结果;更麻烦的是,只要任意一个 mixin 增加新方法(比如都实现了 render()),实际调用哪个就取决于声明顺序和整个继承链的结构,而不是你当前想用的能力。
-
ReportExporter().render()调用的是ExcelMixin.render还是PDFMixin.render?得查ReportExporter.__mro__才敢确认 - 第三方库升级后悄悄改了某个 mixin 的 MRO 位置,你的导出逻辑可能突然走错分支
- 无法在运行时动态切换行为(比如临时禁用日志),因为 mixin 已在类定义期硬编码进去了
子类被迫承担不相关的职责
继承强制传递“全部接口”。一个 PaymentProcessor 子类如果继承自 BaseService,就不得不带上 BaseService 的 health_check()、metrics_report()、retry_policy 等一堆和支付无关的方法——哪怕它永远不调用。这不是复用,是污染。
- IDE 自动补全会列出大量无意义方法,干扰开发注意力
- 单元测试必须 mock 掉所有父类依赖,哪怕只测一个
charge() - 类型提示(如
def handle(p: BaseService))失去表达力:你传的是支付处理器,但类型系统只认它是“某种 BaseService”
组合能精准控制行为注入点
把 fly 抽成 FlyBehavior 类,让 Bird 持有 self.fly_behavior,需要飞的就传 WingsFly(),不需要的传 NoFly()。这样修改行为不用动类结构,扩展新飞行方式也不影响旧代码。
- 行为可替换:测试时直接传入
MockFly(),无需继承或 patch - 职责清晰:
Bird只管“有翅膀”,FlyBehavior管“怎么飞” - 避免菱形继承陷阱:没有 MRO 冲突,也没有 super() 调用顺序焦虑
class A(B, C):,而是判断这个 A 是否真的“是一种 B,同时也是一种 C”——业务语义一旦模糊,继承就从设计工具变成技术债务加速器。Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











