组合是Python业务场景下唯一合理的选择,因继承存在运行时不可替换、MRO失控、语义易错等问题;仅当is-a关系稳定且契约明确时才用继承。

组合比继承更灵活,尤其当你发现子类越来越多、方法重写越来越难维护时,直接用组合能快速解耦。
什么时候该把继承改成组合?
当出现 super().some_method() 链过长、子类只复用部分父类行为、或者要动态切换行为(比如运行时换策略)时,继承已经变成负担。组合的核心是“有”而不是“是”——比如一个 Car 不是 Engine,但它“有”一个 engine 实例。
- 常见错误现象:修改基类导致多个子类意外崩溃,或为了复用一小段逻辑不得不继承整个类
- 典型场景:需要混合不同能力(如可序列化 + 可缓存 + 可重试),但这些能力彼此无关
- 参数差异:
__init__里不再传一堆父类参数,而是按需注入具体对象,例如self.storage = database_backend
如何用组合替换多层继承?
把原来分散在各层父类里的职责,抽成独立类,再在主类中作为属性持有。关键不是删掉父类,而是让主类只负责协调,不负责实现。
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
- 先识别出被继承链反复使用的功能块(如
Logger、Validator、RetryPolicy),各自封装为类 - 主类(如
DataProcessor)的__init__接收这些组件实例,而非继承它们 - 调用时显式委托:
self.validator.validate(data)而不是self.validate(data) - 避免在组合类里暴露过多内部状态——如果
self.engine.rpm频繁被外部读写,说明边界没划清
组合中容易踩的坑
组合不是“随便 new 一堆对象塞进 self”,它对生命周期和责任划分更敏感。
- 忘记传递依赖:比如
FileReader需要path和encoding,但主类初始化时漏传,运行时报AttributeError: 'NoneType' object has no attribute 'read' - 误用共享实例:多个
DataProcessor实例共用同一个Cache对象,却没考虑并发写入冲突 - 过度委托:每个方法都只是
return self._inner.do_xxx(),没做任何协调或转换,说明组合粒度太细,反而增加调用跳转 - 性能陷阱:高频调用中频繁创建组合对象(如每次请求都 new 一个
JSONEncoder),应考虑复用或池化
最难的不是写组合代码,而是判断哪些逻辑该“提出来”,哪些该“留在里面”。边界模糊时,先从最常变化、最易测试的部分开始拆——比如把网络请求逻辑单独成 APIClient,比一开始就重构整个继承树更稳妥。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










