选抽象类还是接口,关键看业务变化方向:同一类事物共性扩展用抽象类,能力自由拼装用接口;前者强调“是什么”并共享状态与逻辑,后者强调“能做什么”支持多实现与正交扩展。

选抽象类还是接口,关键不在语法,而在业务变化的“方向”——是同一类事物在变,还是能力组合方式在变。看准这个,结构才不会随着需求迭代而频繁重构。
看变化是否围绕“同一类事物”展开
如果新增功能始终属于“同一种东西”的范畴,说明分类稳定、共性明确,适合用抽象类。
- 比如所有支付方式都要支持“分账回调”,所有图形都要支持“导出SVG”,所有动物都要新增“冬眠”行为——核心类型没变,只是同类行为在扩展
- 抽象类能自然承载共用状态(如
createTime、name)和可复用逻辑(如统一日志、参数校验模板) - 子类继承后只需专注差异部分;未来加一个默认方法(如
logOperation()),所有子类自动获得,改一处全生效
看变化是否来自“能力自由拼装”
如果业务越来越灵活,能力之间彼此独立、可插拔、不绑定具体类型,就该用接口。
- 例如:订单今天要微信支付+短信通知,明天要支付宝+邮件+风控拦截,后天又加汇率转换——这些能力正交、按需装配
- 一个类可以同时实现
Payable、Notifiable、RiskCheckable,互不影响 - 新加能力(如
Refundable)只影响需要退款的类,其他类完全无感;Java 8+ 的 default 方法还能提供基础实现,避免重复编码
看是否需要约束“谁可以拥有这个能力”
接口更适合表达开放契约,尤其当实现者根本不是同类事物时。
- 比如“所有能登录的模块都必须支持双因素认证”,但登录模块可能是 Web 页面、小程序、IoT 设备端——它们语义上毫无关系,硬塞进同一个抽象类会破坏设计
- 接口不关心“你是什么”,只规定“你能做什么”,不同技术栈、不同团队开发的模块只要实现同一接口,就能被统一调度或注入
- 测试时也容易 mock,比如用
Mockito.mock(Exportable.class)快速构造行为桩
用三句话快速判断
面对新需求,直接问自己:
- 这个功能是不是所有子类“天然就应该有”的?→ 是 → 抽象类
- 这个功能会不会被完全无关的类“按需装配”?→ 是 → 接口
- 将来加新功能时,是否希望“改一处,全生效”,还是“只影响相关类”?→ 前者倾向抽象类,后者倾向接口











