解耦子类与父类强依赖的核心是用组合替代继承、提取纯函数、定义接口契约、保持扩展点开放。例如将 PaymentService 从继承 BaseHttpService 改为持有 HttpClient 接口,测试时注入 MockHttpClient;把 doValidate 等逻辑抽为顶层函数;用 Processor 接口替代继承关系;避免 final 方法封锁测试替换。

解耦子类与父类的强依赖,核心是打破“子类必须继承自特定父类”这一硬绑定,让子类行为可替换、可隔离、可模拟。这直接提升单元测试的可行性——你不再需要启动整个继承链来验证一个方法,也不必为父类副作用(如网络调用、文件读写)做复杂桩处理。
用组合替代继承
避免让子类通过 extends 或 : 强制绑定到具体父类实现。改用“拥有”关系,把原本由父类提供的能力抽象成接口或策略对象,由子类在运行时接收。
- 例如:原设计中
PaymentService extends BaseHttpService,导致每次测试都要处理 HTTP 客户端初始化;改为PaymentService持有HttpClient接口实例,构造时注入 - 测试时传入
MockHttpClient或内存实现,完全绕过网络层 - 父类逻辑若无状态、纯工具性,也可转为顶层函数或扩展函数,进一步消除继承必要性
提取可测试的核心行为为独立函数
将子类中真正承担业务逻辑的方法(尤其是被父类模板方法调用的部分)抽离为顶层函数或静态方法,参数显式接收所有依赖项。
- 比如父类定义了
process()模板方法,内部调用doValidate()和doPersist() - 把这两个方法移到外部,变成
fun validate(input: Input): Result和fun persist(data: Data, db: Database) - 子类只需组合调用,测试时直接对这些函数断言,不涉及任何类层级
用接口或协议定义父子协作契约
不依赖父类类型,而是定义一个轻量接口描述“子类需要提供什么”,父类面向该接口编程;子类只需实现接口,无需继承。
- 例如定义
Processor接口含handle(event: Event): Boolean - 原父类
EventDispatcher持有Processor列表并调用其方法,不再要求子类是它的继承者 - 测试时可用匿名对象、lambda 或
object : Processor { ... }快速构造行为可控的实现 - Go、Python(Protocol)、Kotlin(interface)、C++(abstract class + override)均支持这种隐式/显式契约
避免在父类中使用 final 方法或非虚函数封锁扩展点
如果父类强制子类覆盖某个方法,但该方法又被标记为不可重写,就会形成测试盲区——你无法替换成模拟行为。
- 检查父类中是否有
final(Java/Kotlin)、sealed(C#)、override final(C++)等限制 - 对关键扩展点保持开放,允许子类在测试中返回预设值或抛出特定异常
- 必要时提供受控的钩子(hook),如
onBeforeProcess(): Unit,默认空实现,测试时可重写

















