应将深层继承改为组合,通过提取接口、声明私有字段、委托调用、构造器注入四步迁移;对需多态的场景改用策略模式;类型兼容问题通过顶层接口、职责拆分或包装器解决。

继承树过深(比如超过两层)会让代码变得脆弱、难测、难改——改一个父类方法,十几个子类行为可能意外偏移;写个单元测试,得先初始化整条继承链;加个新子类,还得反复检查有没有覆盖错钩子方法。用组合替代深层继承,不是简单换语法,而是把“它是一种”关系,换成“它用到了什么”,从而切断隐式依赖。
识别该重构的典型信号
出现以下情况,说明继承结构已偏离语义,适合转向组合:
- 某层类名含 Service、Base、Wrapper、Cached 等技术性词汇,而非领域概念(如 PaymentMethod → OnlinePayment → WechatPay 是合理分层;而 BaseController → AuthController → AdminAuthController 就是抽象失焦)
- 子类只重写一两个方法,其余全部调用
super.xxx(),且不参与多态调度 - 父类里大量
protected字段或方法,子类频繁访问实现细节 - 构造器中调用被子类重写的 hook 方法,导致
NullPointerException(因子类字段尚未初始化)
四步完成继承→组合迁移
以一个常见误用为例:ReportExporter extends DatabaseClient extends NetworkClient(报表导出器“是一个”网络客户端?显然不合理):
-
抽能力为接口+实现类:把数据库操作提取为
DbClient接口,提供query()、executeUpdate()等契约;再由MySqlClient、PostgreClient各自实现 -
在目标类中声明私有字段:删除
extends,改为private final DbClient db; -
用委托代替继承调用:原
super.query(...)改为db.query(...);所有对外方法(如exportMonthlyReport())内部只调用该字段 -
构造器注入依赖:定义
public ReportExporter(DbClient db),禁止内部new实例,方便测试与替换
保留多态能力的策略模式替代法
如果原继承是为了运行时切换行为(如不同支付方式),不要删掉多态,而是用组合封装变化点:
- 定义
interface PaymentStrategy,各实现类(AlipayStrategy、WechatStrategy)独立封装逻辑 -
PaymentProcessor类持有private PaymentStrategy strategy - 通过构造器或 setter 注入策略,支持运行时动态切换,避免新增子类
- 测试时只需 mock 单个
PaymentStrategy,无需模拟整棵继承树
处理类型兼容与语义表达
组合后不再天然满足 is-a,但业务常需统一类型处理。实际做法更轻量:
- 定义顶层接口(如
Exporter、Validator),所有组合类直接implements它,而非继承抽象基类 - 按职责拆小接口(
SyncExporter、AsyncExporter),类按需实现,避免接口污染 - 若旧代码强依赖继承体系(如 Spring Bean 类型匹配),可用包装器模式:新建
LegacyExporterWrapper extends ExporterBase,内部持组合对象并转发调用
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











