应优先用组合、接口和职责分离替代继承,仅对真实“is-a”关系使用继承;避免为复用而拉长继承链、父类掺杂子类逻辑或暴露无关功能;通过private final字段组合能力对象并注入;共性行为用接口+default方法提取;遗留系统分三步渐进解耦。

避免继承导致耦合过深,核心是把“我是一个什么”转为“我拥有什么”,用组合、接口和职责分离来替代无节制的 extends。
只对真实 is-a 关系使用继承
继承表达的是语义上的“是”,不是功能上的“能”。比如 Dog 是 Animal 合理,但 Order 是 Exportable 不成立——Order 能导出,不代表它“是”一种导出物。这类能力应通过接口声明,由 Order 实现即可。
检查每个父类:能否用自然语言说清“X 是 Y”?不能,就该拆解或替换为组合。
- 禁止为复用方法而拉长继承链(如 Vehicle → Machine → Device)
- 避免在父类里塞子类专属逻辑,比如用 if (type == "email") 分支适配不同子类
- 父类不应承担子类细节,也不该暴露与自身身份无关的功能(如日志、缓存、重试)
用组合替代深层继承
当子类仅为了复用某部分能力而继承时,实际关系已偏离语义,此时应切断继承链,改用 private final 字段持有能力对象,并通过构造器注入。
- 原写法:
class OrderService extends BaseService extends NetworkClient - 新写法:
class OrderService { private final NetworkLayer network; OrderService(NetworkLayer network) { this.network = network; } } - 调用从
super.send(req)改为network.send(req) - 不提供
getNetwork()这类透出内部实现的方法,封装才真正生效
用接口 + default 方法提取共性行为
多个不相关类需要共享逻辑(如日志、校验、序列化),不要硬造共同父类,而是定义接口并提供 default 方法。
- 定义
interface Loggable { default void log(String msg) { System.out.println(msg); } } - Cat、Order、Report 各自
implements Loggable,按需覆盖 - 既避免继承污染,又支持多实现,还保持调用一致性
- 下游依赖面向接口编程,而非具体父类,替换实现(如 OkHttp 替换 HttpUrlConnection)不再牵连整个继承链
分阶段迁移遗留系统
老项目不能一刀切删除继承,要保障行为不变的前提下逐步解耦:
- 第一步:给顶层父类加对应接口,让新代码先依赖接口
- 第二步:把子类中对父类 protected 方法的调用,逐步替换成对组合对象的委托
- 第三步:确认所有单元测试通过后,再删掉冗余的继承关系和空父类
- 每一步都以测试绿灯为前提,不破坏现有行为是重构底线
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











