Python组合与继承如何选择_基于“HAS-A”与“IS-A”原则重构代码

夏杰姑娘_9099

夏杰姑娘_9099

2026-04-26

516人浏览

原创

当子类与父类存在明确is-a关系且符合liskov替换原则时才用继承;否则优先组合,通过依赖注入实现松耦合与可测试性。

python组合与继承如何选择_基于“has-a”与“is-a”原则重构代码

什么时候该用继承而不是组合

当子类和父类之间存在明确的 IS-A 关系,且子类确实“是”父类的一种时,继承才合理。比如 Dog 是 Animal,FileReader 是 Reader——它们共享行为契约,能被同一接口安全替换(Liskov 替换原则)。

常见误用:为复用代码强行继承 BaseLogger 或 ConfigMixin,结果子类既不是 logger 也不是 config,只是“用到了日志或配置”。这时继承破坏语义,还导致子类被迫暴露不相关接口。

  • 检查是否能自然说出“X 是 Y 的一种”,不能就别继承
  • 若只是为了调用父类方法,优先考虑把逻辑抽成独立函数或工具类
  • 继承后子类必须能完全替代父类出现在所有使用父类的地方,否则迟早出 TypeError 或逻辑错乱

组合的典型写法与初始化陷阱

组合本质是“HAS-A”,比如 Car 有 Engine,OrderService 有 PaymentGateway。关键在初始化时机和依赖注入方式。

错误写法是直接在 __init__ 里硬编码实例:self.engine = Engine()。这导致测试难、替换难、生命周期耦合紧。

  • 把依赖作为参数传入 __init__,允许外部控制创建方式(如 mock 或不同实现)
  • 避免在组合对象内部调用依赖的私有方法(如 engine._start_internal()),这会泄露实现细节
  • 注意循环引用:A 组合 B,B 又组合 A —— Python 不报错但 GC 可能延迟回收,用 weakref 或重构拆分职责

@property 和 __getattr__ 在组合中的边界

组合后常想“透传”依赖的方法,比如让 car.start() 实际调用 car.engine.start()。但盲目转发会模糊责任归属,也容易掩盖类型错误。

testing-python
testing-python

使用pytest编写和评估有效的Python测试。适用于编写测试、审查测试代码、调试测试失败或提高测试覆盖率。

下载

@property 适合封装状态代理(如 car.fuel_level 映射到 self.tank.level);而 __getattr__ 是兜底机制,只应在明确知道缺失属性必然属于某个组合对象时使用。

  • 用 @property 时,确保返回值类型稳定,不要让它动态返回不同类的实例
  • __getattr__ 中必须先检查目标对象是否存在该属性,否则可能无限递归(return getattr(self._engine, name) 要加 hasattr(self._engine, name) 判断)
  • IDE 和类型检查器(如 mypy)对 __getattr__ 不友好,建议配合 typing.overload 或文档说明可代理的方法

从继承迁移到组合的实际步骤

已有继承结构想改成组合,别重写,用渐进式替换。核心是把原父类功能剥离为独立组件,再注入到原子类中。

例如原 class ReportGenerator(HtmlRenderer):,现在要解耦渲染逻辑。先提取 HtmlRenderer 为普通类,去掉 class HtmlRenderer: 中的 pass 父类声明,再让 ReportGenerator 持有它:

class ReportGenerator:
    def __init__(self, renderer=None):
        self._renderer = renderer or HtmlRenderer()
    
    def render(self):
        return self._renderer.render_html(self.data)

之后可轻松切换为 MarkdownRenderer 或加 mock 测试。

  • 迁移时保留原继承接口(如仍提供 render() 方法),避免下游代码批量报错
  • 原父类中带状态的属性(如 self.cache)要一并移到组合对象里,不能留在子类中双份维护
  • 如果原父类有 __init__ 参数,这些参数应转为组合对象的构造参数,而非子类的参数——否则语义混乱

组合不是银弹,它让关系更清晰,但也增加一层间接性。真正难的不是语法怎么写,而是判断哪个对象该负哪部分责——这点没法靠工具检查,得看代码跑起来时谁改一点,另一处就莫名崩了。

Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!

相关专题

更多
python打包成可执行文件
python打包成可执行文件

本专题为大家带来python打包成可执行文件相关的文章,大家可以免费的下载体验。

2023.07.20

1691

4

python能做什么
python能做什么

python能做的有:可用于开发基于控制台的应用程序、多媒体部分开发、用于开发基于Web的应用程序、使用python处理数据、系统编程等等。本专题为大家提供python相关的各种文章、以及下载和课程。

2023.07.25

4244

7

format在python中的用法
format在python中的用法

Python中的format是一种字符串格式化方法,用于将变量或值插入到字符串中的占位符位置。通过format方法,我们可以动态地构建字符串,使其包含不同值。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

2023.07.31

1689

3

python教程
python教程

Python已成为一门网红语言,即使是在非编程开发者当中,也掀起了一股学习的热潮。本专题为大家带来python教程的相关文章,大家可以免费体验学习。

2023.08.03

24717

23

python环境变量的配置
python环境变量的配置

Python是一种流行的编程语言,被广泛用于软件开发、数据分析和科学计算等领域。在安装Python之后,我们需要配置环境变量,以便在任何位置都能够访问Python的可执行文件。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.04

3027

5

python eval
python eval

eval函数是Python中一个非常强大的函数,它可以将字符串作为Python代码进行执行,实现动态编程的效果。然而,由于其潜在的安全风险和性能问题,需要谨慎使用。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.04

3047

5

scratch和python区别
scratch和python区别

scratch和python的区别:1、scratch是一种专为初学者设计的图形化编程语言,python是一种文本编程语言;2、scratch使用的是基于积木的编程语法,python采用更加传统的文本编程语法等等。本专题为大家提供scratch和python相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.11

1163

5

python合并两个列表
python合并两个列表

Python是一种强大的编程语言,具有许多方便的功能和工具。在Python中,有多种方法可以合并两个列表。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.10

596

4

python是前端还是后端
python是前端还是后端

Python属于前端也属于后端,其灵活性和丰富的生态系统使得开发人员能够在不同的领域中灵活运用。本专题为大家提供python相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.11

2363

5

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程