接口定义“能做什么”,抽象类封装“怎么一步步做”;接口划清能力边界、隔离变化,抽象类固化流程骨架、复用稳定逻辑,二者应按职责组合使用而非语法取舍。

规划系统顶层设计时,接口与抽象类不是语法选择题,而是职责划分问题:接口定义“能做什么”,抽象类封装“怎么一步步做”。关键不在用哪个,而在每个模块该承担什么角色。
用接口划清能力边界,隔离变化源头
当多个模块需要协作但彼此不该知道对方细节时,接口是第一道防线。比如消息通知系统,业务层不关心发的是短信、邮件还是微信,只依赖一个 NotificationService 接口:
- 接口只声明
send(Recipient, Content)和isAvailable()这类无状态契约 - 所有实现类(
SmsNotifier、WechatNotifier)互不影响,新增渠道不改已有代码 - 上层可统一注入、切换、Mock,甚至运行时按策略路由
用抽象类固化流程骨架,复用稳定逻辑
当一组子类共享固定执行步骤,仅局部行为不同,就该用抽象类。典型如数据导出服务:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
-
AbstractExporter定义模板方法export():先校验 → 再查询 → 然后格式化 → 最后落盘 - 其中
validate()和saveToFile()是已实现的通用逻辑 -
fetchData()和format()声明为 abstract,由ExcelExporter和PdfExporter各自实现
组合使用:接口暴露能力,抽象类交付默认实现
JDK 8+ 的 default 方法不是让接口变抽象类,而是补小缺口。设计时要守住分寸:
- 接口里的 default 方法只做无副作用的简单操作,比如
toString()的空值兜底、isNotBlank()这类工具逻辑 - 涉及资源管理(如数据库连接)、状态维护(如重试计数)、构造参数依赖的逻辑,必须放进抽象类——接口没有构造器,也无法持有实例字段
- 例如
PaymentProcessor是接口,AbstractAlipayProcessor封装签名生成、异步回调验证等共用流程
避开常见设计反模式
顶层设计一旦错位,后期重构成本极高:
- 把本该是能力契约的场景写成抽象类(如
Flyable设计为抽象类),会让子类被迫继承无关字段或生命周期方法 - 在一个接口里堆砌十多个方法,导致实现类大量空方法重写——应拆成
Readable、Searchable、Exportable等细粒度接口 - 抽象类里塞满 final 方法或静态工具方法,等于提前锁死扩展点,违背开闭原则
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










