Python 组合优于继承的实践示例
发布时间 - 2026-01-30 00:00:00 点击率:次该用 class A 包含 B() 而不是 class A(B) 当只需部分父类行为且需灵活替换、测试隔离或避免强耦合时;继承会绑定全部接口与生命周期,易因 B 变更导致 A 失效。
什么时候该用 class A 包含 B() 而不是 class A(B)
当子类只需要部分父类行为,且这些行为可能随上下文变化、需要替换或测试隔离时,组合更合适。继承会把 B 的全部接口和生命周期强绑定到 A,一旦 B 内部改了(比如加了新方法或修改了 __init__ 参数),A 就可能意外出错。
常见信号包括:
- 你只调用
B的 1–2 个方法,却继承了它全部 20 个方法和属性 -
A的测试需要 mockB的行为——用继承时只能 patch 类方法或绕过super().__init__,而组合直接传入 mock 实例即可 -
B是第三方类(如requests.Session或sqlite3.Connection),你无法控制其演进
__init__ 里传实例比在类里硬编码 B() 更灵活
硬编码 self.db = Database() 会让单元测试难写,也堵死了换存储后端的路。应该把依赖显式传进来,哪怕默认给一个。
class UserService:
def __init__(self, db=None):
self.db = db or Database() # 可选,默认构造
这样调用时可以自由替换:
- 测试:
UserService(MockDB()) - 开发:
UserService(InMemoryDB()) - 生产:
UserService(PostgresDB(host="..."))
注意:不要在 __init__ 里做重操作(如连接数据库),否则组合对象初始化失败时难以定位是自身问题还是依赖问题。
用属性委托代替继承后的方法转发
继承后如果只转发几个方法(比如 def save(self, *a): return self.db.save(*a)),不如用 __getattr__ 动态代理——减少样板代码,还能统一处理未实现方法。
class Repository:
def __init__(self, backend):
self._backend = backend
def __getattr__(self, name):
return getattr(self._backend, name)
但要注意:
-
__getattr__不拦截已定义的属性或方法(如__init__、__str__),所以不会覆盖你自己的逻辑 - 如果
_backend某方法抛AttributeError,会被吞掉并转抛为Repository的 AttributeError,调试时容易误判来源 - IDE 和类型检查器(如 mypy)可能无法推导动态代理的方法,需配
typing.overload或Protocol
组合后别忘了生命周期管理
继承时,父类资源通常和子类共生死;组合则要自己管。比如 self.session = requests.Session(),你得确保它被关闭——否则连接泄漏。
- 用上下文管理器:
with UserService(db) as service:,在__enter__/__exit__中控制db.close() - 或者暴露显式清理方法:
service.close(),并在文档里强调“必须调用” - 避免在
__del__里关资源——不可靠,且可能触发循环引用
最易忽略的是:组合对象被长期持有(如全局单例或 Flask 应用中的 g 对象)时,底层依赖的连接/文件句柄是否复用合理、有没有超时重连逻辑。
# python
# 编码
# session
# 后端
# 动态代理
# flask
# 父类
# 子类
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
如何在HTML表单中获取用户输入并结合JavaScript动态控制复利计算循环
Laravel如何升级到最新版本?(升级指南和步骤)
网站视频制作书签怎么做,ie浏览器怎么将网站固定在书签工具栏?
常州企业网站制作公司,全国继续教育网怎么登录?
做企业网站制作流程,企业网站制作基本流程有哪些?
javascript基于原型链的继承及call和apply函数用法分析
java ZXing生成二维码及条码实例分享
Win11搜索不到蓝牙耳机怎么办 Win11蓝牙驱动更新修复【详解】
香港服务器网站搭建教程-电商部署、配置优化与安全稳定指南
C++时间戳转换成日期时间的步骤和示例代码
Laravel如何使用Blade模板引擎?(完整语法和示例)
武汉网站设计制作公司,武汉有哪些比较大的同城网站或论坛,就是里面都是武汉人的?
Midjourney怎样加参数调细节_Midjourney参数调整技巧【指南】
如何在云服务器上快速搭建个人网站?
Laravel任务队列怎么用_Laravel Queues异步处理任务提升应用性能
Laravel如何实现邮件验证激活账户_Laravel内置MustVerifyEmail接口配置【步骤】
微信小程序 scroll-view组件实现列表页实例代码
Laravel如何处理JSON字段的查询和更新_Laravel JSON列操作与查询技巧
,在苏州找工作,上哪个网站比较好?
JavaScript如何实现继承_有哪些常用方法
Laravel如何将应用部署到生产服务器_Laravel生产环境部署流程
如何在腾讯云免费申请建站?
Python结构化数据采集_字段抽取解析【教程】
Laravel怎么集成Log日志记录_Laravel单文件与每日日志配置及自定义通道【详解】
邀请函制作网站有哪些,有没有做年会邀请函的网站啊?在线制作,模板很多的那种?
如何确保FTP站点访问权限与数据传输安全?
如何快速搭建虚拟主机网站?新手必看指南
C#如何调用原生C++ COM对象详解
Laravel如何使用软删除(Soft Deletes)功能_Eloquent软删除与数据恢复方法
Laravel如何实现API版本控制_Laravel API版本化路由设计策略
使用C语言编写圣诞表白程序
Laravel如何配置任务调度?(Cron Job示例)
如何在企业微信快速生成手机电脑官网?
网站制作大概多少钱一个,做一个平台网站大概多少钱?
成都网站制作公司哪家好,四川省职工服务网是做什么用?
Laravel如何使用Passport实现OAuth2?(完整配置步骤)
Laravel怎么使用Session存储数据_Laravel会话管理与自定义驱动配置【详解】
如何快速生成高效建站系统源代码?
如何在阿里云高效完成企业建站全流程?
Laravel请求验证怎么写_Laravel Validator自定义表单验证规则教程
JavaScript如何实现错误处理_try...catch如何捕获异常?
中山网站推广排名,中山信息港登录入口?
Laravel如何理解并使用服务容器(Service Container)_Laravel依赖注入与容器绑定说明
Android利用动画实现背景逐渐变暗
Laravel如何生成和使用数据填充?(Seeder和Factory示例)
在Oracle关闭情况下如何修改spfile的参数
Laravel怎么配置.env环境变量_Laravel生产环境敏感数据保护与读取【方法】
Laravel如何实现图片防盗链功能_Laravel中间件验证Referer来源请求【方案】
专业企业网站设计制作公司,如何理解商贸企业的统一配送和分销网络建设?
Laravel如何使用Laravel Vite编译前端_Laravel10以上版本前端静态资源管理【教程】


