抽象类与接口是分层协作搭档:接口定边界、代表“谁能做什么”,用于定义稳定对外契约和多能力组合;抽象类填骨架、代表“它是什么”,封装同源类的通用状态、流程与默认行为;具体类做适配,专注差异实现。

抽象类和接口不是非此即彼的选择,而是分层协作的搭档:接口定边界、抽象类填骨架、具体类做适配。
用接口定义稳定对外契约
接口代表“谁能做什么”,是系统对外承诺的行为规范,一旦发布就尽量不改签名。
- 所有服务入口(如OrderService)、回调通知(如NotificationHandler)、数据转换逻辑(如Serializer
)优先定义为接口 - 跨领域能力(日志、审计、序列化)统一抽为接口,不同模块可自由实现,互不耦合
- 一个实体需组合多种能力(如订单既要可审计、又要可导出、还要可校验),必须用接口——Java 不支持多继承,但支持多实现
用抽象类封装同源类的共性逻辑
抽象类代表“它是什么”,承载同一族类的通用状态、初始化流程和默认行为。
- 多个实现类天然属于同类事物(如PDFExporter、ExcelExporter),且共享字段(fileName、timeout)、资源(outputStream)、流程(prepare → execute → cleanup),就该用抽象类
- 想强制子类走固定流程但允许定制关键步骤?直接落地模板方法模式,比如AbstractReportGenerator定义generate()模板,留出fetchData()和formatResult()由子类实现
- 需要控制子类构造方式(比如必须传入Config或Executor)——抽象类有构造器,接口没有
二者配合构建三层结构
真实项目里,接口和抽象类共同组成清晰的抽象层次:
- 先定义顶层接口(如CacheClient),只声明get()、put()、evict()等契约,供上层调用和测试桩使用
- 再提供抽象实现类(如AbstractRedisCacheClient),封装连接池管理、序列化、重试逻辑、监控埋点等通用功能
- 最后由具体类(如ClusterRedisCacheClient、StandaloneRedisCacheClient)专注适配差异点:集群路由、节点发现、故障转移策略
避开常见设计偏差
有些做法看似抽象,实则破坏架构清晰性:
- 不要为了“看起来更抽象”而把所有父类都做成抽象类——如果只是空壳、无字段、无默认逻辑、无构造约束,那它大概率该是接口
- 不要在接口里塞大量default方法来替代抽象类——default方法适合补丁式扩展,不是主干逻辑载体;它不能带字段、不能设protected成员,复用能力有限
- 已有类层次较深时,别硬插一层抽象类去“统一”——强行抽象反而增加理解成本,不如按业务本质重新划分职责
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











