父类依赖注入、继承与方法重写是一套协同设计逻辑:di解决“用谁”,继承解决“从哪来”,重写解决“怎么改”;关键在于避免陷阱,如父类构造器中调用被重写方法却执行旧实现,需确保di先于继承发生、子类显式传入依赖、重写时合理调用super、并验证多态是否生效。

父类依赖注入、继承与方法重写不是三个孤立操作,而是一套协同工作的设计逻辑:依赖注入解决“用谁”,继承解决“从哪来”,重写解决“怎么改”。真正落地时,关键不在语法本身,而在三者如何配合避免常见陷阱——比如子类重写了方法,但父类构造器里已调用该方法,结果运行的仍是旧实现。
依赖注入要先于继承发生
依赖注入(DI)本质是把外部对象“塞进来”,而不是在类内部 new 出来。它必须在继承链建立前完成,否则子类无法参与依赖决策。
- 不要在父类构造函数里直接初始化服务实例,而是接收接口或抽象类型参数
- 子类构造器应显式调用父类构造器,并传入自己准备好的依赖项(如 mock 实例、配置化服务)
- 推荐使用构造器注入而非 setter 或字段注入,确保依赖不可变且一创建就完整
继承结构要明确“is-a”边界
只有当子类确实是父类的一种具体形态时才用继承。例如 PaymentService 和 WechatPaymentService 是合理的;但若只是“用到了支付能力”,就该用组合而非继承。
- 父类应聚焦通用契约:定义抽象方法、提供默认行为、封装公共状态
- 子类只负责填充差异逻辑,不破坏父类原有流程语义(比如父类
process()包含日志+校验+执行,子类重写execute()即可,不该绕过校验) - 语言层面注意修饰符限制:Kotlin 要加
open,TypeScript 默认public但方法需显式override,Ruby 无需关键字但需同名定义
重写方法时保留父类调用入口
很多问题源于重写后切断了父类逻辑链。真正健壮的做法不是“覆盖”,而是“增强”。
- 用
super(Ruby/Python)、super.xxx()(Java/Kotlin/TypeScript)显式调用父类同名方法 - 如果父类方法含关键前置校验或后置清理,子类重写时应在开头或结尾补上
super调用 - Go 是个例外:嵌入结构体不等于继承,
a.Foo()永远调自己的方法,想动态分发得靠接口+组合,不是靠字段嵌入
实操检查清单
每次写完继承+DI+重写组合,快速核对这四点:
- 依赖是否通过构造器传入?有没有在类内部 new 任何服务?
- 父类方法是否声明为可重写(
open/virtual/无 final 修饰)? - 子类重写的方法里,是否遗漏了必要的
super调用? - 运行时实际调用的是哪个类的方法?可通过打日志或断点验证多态是否生效











