抽象类应作为“骨架模板”表达“是什么”,接口作为“能力插槽”定义“能做什么”,通过组合而非继承实现灵活扩展;避免抽象类膨胀,将可变行为、第三方适配等拆为接口并注入,恪守单一职责。

抽象类本身是继承机制的产物,但“组合优于继承”不是要抛弃抽象类,而是提醒你:别为了复用而硬套继承,更别让抽象类变成万能父类。真正平衡抽象类与接口选型的关键,在于把抽象类当“骨架模板”,把接口当“能力插槽”,再用组合把它们串起来——而不是堆砌继承链。
明确抽象类的定位:只承载“是什么”的共性,不塞进“能做什么”
抽象类适合表达强层级关系,比如 PaymentProcessor 作为所有支付方式的基类,它定义了“是支付行为”这一本质,并提供通用日志、幂等校验、统一异常包装等可复用逻辑。但它不该定义“支持微信回调”或“支持支付宝扫码”这类具体能力——这些属于“能做什么”,应抽成 CallbackHandler、QrCodeGenerator 等独立接口,由具体子类通过组合引入。
- 抽象类里只放真正被所有子类共享的状态(如
protected final Logger logger)和逻辑(如validateOrder()) - 把可变行为、第三方适配、扩展点全部拆成接口,让子类按需组合实现
- 避免在抽象类中添加大量
abstract方法来“预留扩展”,那其实是接口该干的事
用接口定义能力契约,再通过组合注入到抽象类体系中
比如一个 AbstractNotificationService 抽象类负责统一消息组装、渠道路由、失败重试框架;但它不直接实现“发短信”“推APP”“发邮件”。这些能力分别定义为 SmsSender、AppPusher、EmailClient 接口。子类如 OrderNotificationService 在构造时注入所需组件:
public class OrderNotificationService extends AbstractNotificationService {private final SmsSender smsSender;private final AppPusher appPusher;public OrderNotificationService(SmsSender sms, AppPusher push) { ... }}
这样既保留了抽象类对核心流程的约束,又通过组合获得灵活装配能力,还方便单元测试(可注入 mock 实现)。
Java JDK 25 来自 OpenJDK 官方归档,版本为 JDK 25,本条下载地址已指向官方 Windows x64 zip 安装包直链,适合调试旧项目或兼容旧版 Java 运行环境。
警惕抽象类膨胀:当发现它开始承担“多角色”时,就是拆接口+组合的信号
如果抽象类里出现以下情况,说明它已偏离初衷,该重构了:
- 方法签名越来越多,且彼此语义无关(比如既有
parseXml()又有encryptAes()还有sendKafka()) - 子类只重写其中一两个方法,其余全靠空实现或 throw UnsupportedOperationException
- 需要为不同子类添加条件分支(
if (type == X) {...} else if (type == Y) {...})来区分行为
这时应把各职责拆成独立接口,让抽象类持有它们的引用,用策略模式或简单委托调用,而非靠继承继承再继承。
JDK 8+ 后,别用 default 方法替代组合
接口有了 default 方法,容易让人误以为“反正能写实现,干脆全塞进接口”。但 default 方法本质仍是契约的一部分,不适合承载状态或复杂流程。比如:
- ✅ 合理:
interface Retryable { default void retry(Runnable task) { /* 简单重试逻辑 */ } } - ❌ 风险:
interface Payment { default void process() { /* 初始化连接 + 执行扣款 + 记日志 + 发MQ */ } }—— 这实际是把本该由抽象类或组合封装的流程,硬塞进接口,破坏单一职责
真正需要共享实现 + 状态的,还是交给抽象类;需要横向能力复用的,交给组合;接口只管声明“这个东西能 retry”“这个东西能 notify”,不管它怎么做到。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










