避免滥用继承的关键是严格遵循“is-a”业务语义,优先用组合替代继承,确保符合里氏替换原则,慎用模板方法,杜绝为测试或复用而继承。

避免滥用继承的关键,是把“is-a”关系真正落实为业务语义上的严格归属,而不是为了代码复用或结构相似就强行拉关系。
优先用组合代替继承
当类之间只是“有”某种能力或行为(比如“汽车有发动机”),而不是“是”某种类型(比如“宝马是汽车”),就该用组合。继承会把父类的实现细节一并拖进来,耦合太紧;组合则只依赖接口或具体类的行为,更灵活、更易测试。
- 把共用逻辑封装成独立类,通过字段持有它,而不是继承它
- 用接口定义能力契约(如 Runnable、Comparable),让类去实现,而非继承抽象基类
- Spring 的 DataSource、TransactionManager 都是组合驱动的设计范例
继承必须满足里氏替换原则(LSP)
子类对象必须能无缝替换父类对象,且不改变程序正确性。一旦发现子类要重写父类方法并抛异常、返回 null、缩小参数范围、扩大返回类型,或者不得不在调用前加 instanceof 判断——说明继承已违背 LSP,该重构了。
- 子类不能削弱父类的前置条件(输入约束不能更严)
- 子类不能加强父类的后置条件(输出保证不能更弱)
- 不要让子类“拒绝实现”父类声明的方法(比如抛 UnsupportedOperationException)
慎用模板方法模式,警惕抽象类膨胀
抽象类本意是定义骨架,但容易变成“所有子类都要改”的中心节点。一旦新增一个钩子方法,就得同步更新全部子类;一旦修改模板逻辑,风险波及整个继承树。
立即学习“Java免费学习笔记(深入)”;
- 抽象类只保留真正稳定、不可变的核心流程,其余行为尽量交给策略接口注入
- 考虑用函数式接口 + Lambda 替代固定步骤的抽象类(如 Consumer
、Function ) - 如果抽象类已有 3 个以上非 final 方法,或子类重写率超 60%,就要评估是否该拆成组合+策略
禁止为“方便测试”或“减少代码量”而继承
继承不是快捷键。为绕过 private 访问、为少写几个 getter、为 mock 父类方法而继承,都会污染领域模型。单元测试应面向接口或行为,而不是靠继承钻访问漏洞。
- 测试时用 Mockito 的 @Mock 或 spy,而不是继承被测类来覆盖方法
- 工具类、常量类、DTO 类都不该被继承——它们不是类型,只是载体
- 如果只是为了复用某段逻辑,提取为 static 工具方法 或 default 接口方法 更安全
基本上就这些。继承本身没错,错的是把它当成万能胶水。想清楚“这个子类真的属于父类的分类体系吗”,比急着写 extends 要紧得多。











