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 的测试需要 mock B 的行为——用继承时只能 patch 类方法或绕过 super().__init__,而组合直接传入 mock 实例即可
  • B 是第三方类(如 requests.Sessionsqlite3.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.overloadProtocol

组合后别忘了生命周期管理

继承时,父类资源通常和子类共生死;组合则要自己管。比如 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以上版本前端静态资源管理【教程】