
Python中该用继承还是组合,关键看“是不是”还是“有没有”的关系。如果是“子类本质上就是父类的一种”,比如Dog是Animal,适合继承;如果只是“某个类需要使用另一个类的功能”,比如Car有Engine,就该用组合。
继承:语义清晰,但耦合度高
继承表达的是is-a关系,能复用代码、支持多态,但会把子类和父类绑得过紧。一旦父类接口变动,所有子类都可能受影响。
- 父类不该为子类预留太多钩子方法(如
before_save),否则说明设计已偏离单一职责 - 避免多层深继承(如A→B→C→D),超过三层就该考虑拆解或改用组合
- 用
abc.ABC和@abstractmethod明确抽象契约,比靠文档约定更可靠
组合:灵活可控,推荐优先使用
组合体现has-a或uses-a关系,通过成员变量引用其他对象,行为委托给它。修改内部实现不影响外部接口,也便于单元测试(可轻松注入 mock)。
- 把可变行为抽成独立类(如
Notifier、Validator),通过构造函数或 setter 注入 - 配合
__getattr__可实现轻量级代理,但别滥用——可读性下降时不如显式委托方法 - 标准库大量使用组合,比如
threading.Thread接受target函数而非强制继承
一种务实的混合策略
不必非此即彼。常见模式是:用继承定义领域核心类型体系(如Expression → BinaryOp / Literal),再用组合封装横切关注点(如logging、cache、retry)。
立即学习“Python免费学习笔记(深入)”;
- 写框架或 DSL 时,适度继承利于统一接口;写业务逻辑时,组合更能应对需求变化
- 当发现子类只重写一两个方法、其余全靠父类,且这些方法语义不统一,大概率该转为组合+策略模式
- 用
typing.Protocol替代继承做鸭子类型约束,既保灵活性,又得类型检查支持
设计不是选对错,而是权衡演化成本。Python 的动态性让组合天然友好,多数场景下,先写组合,等真正出现稳定的“是一种”关系时,再谨慎引入继承。










