抽象类应限于清晰稳定的“is-a”关系,其余场景优先用组合;避免将抽象类变成工具箱,勿让业务类继承CRUD基类,而应持有Repository委托;多能力需求用接口+组合替代多重继承;模板方法可被函数式接口取代;抽象类内部也应通过组合封装细节。

抽象类本身是为继承设计的,但“组合优于继承”原则并不排斥抽象类,而是提醒你:别为了复用代码或强行建模就滥用抽象类继承。真正该用抽象类的地方,是当多个子类确有清晰、稳定的“is-a”关系,且父类能提供有意义的共性实现;其余场景,哪怕用了抽象类,也应优先考虑用组合来组织能力。
抽象类不该成为“工具箱集合体”
常见错误是定义一个含大量通用方法(如日志、校验、缓存)的抽象基类,让业务类去继承它。这看似省事,实则把无关职责强绑在一起,破坏单一职责。比如:
- 一个 OrderService 不该
extends BaseCrudService来获得 save() 和 delete() —— 它不是“一种 CRUD 服务”,只是需要 CRUD 能力; - 更合理的是持有
private final CrudRepository repository,通过委托调用,职责分明,也方便替换实现。
用接口 + 组合替代“多抽象类继承”需求
Java 不支持多继承,但常有类需同时具备多种核心能力(如可审计、可重试、可熔断)。此时不要试图用抽象类模拟多重继承,而应:
- 将每种能力定义为接口(
Auditable、Retryable、CircuitBreakerAware); - 在具体类中组合对应组件(
private final Auditor auditor、private final RetryPolicy retryPolicy); - 构造时注入,运行时可灵活切换策略,不污染类层次。
抽象模板方法可被函数式组合取代
传统抽象类常用模板方法模式(如 AbstractList 的 get() 调用子类实现的 get(int))。若算法骨架稳定但步骤行为易变,组合更可控:
立即学习“Java免费学习笔记(深入)”;
- 把可变步骤抽成函数式接口(如
Function<item result></item>或自定义Validator); - 在具体类中持有一个或多个该类型字段;
- 执行时直接调用,无需子类继承并重写方法,测试时也只需 mock 接口实例。
抽象类内部也可用组合封装细节
即使保留抽象类结构,其内部实现也应遵循组合原则。例如:
- 一个
AbstractPaymentProcessor不应在子类里暴露doActualCharge()这类底层细节; - 而应持有
private final PaymentGateway gateway和private final FraudChecker checker; - 模板方法
process(PaymentRequest req)内部按序委托调用,子类只决定用哪个 gateway 实现,不碰协议细节。


















