抽象类适用于“同类事物的共性在变”,接口适用于“能力组合方式在变”;前者承载共享状态与逻辑,后者支持正交拼装与跨类型契约;实际项目中常协同使用,抽象类搭骨架、接口填血肉。

抽象类和接口不是语法上的替代选项,而是应对两类不同业务变化的建模工具:一类是“同类事物的共性在变”,另一类是“能力组合方式在变”。选错会导致后续每次新增需求都要重构继承结构,而选对能让扩展自然发生、不惊动已有代码。
看业务变化是否围绕“同一类事物”展开
如果新需求始终落在“同一种东西”的范畴内,说明分类体系稳定,只是行为或状态在演进。这时抽象类更合适。
- 比如所有支付方式都要支持“分账回调”,所有图形都要增加“导出PDF”功能——核心实体没变,只是共性能力在延伸
- 抽象类能天然承载共享字段(如orderNo、createTime)和可复用逻辑(如统一幂等校验、操作日志记录)
- 未来加一个默认方法(如logPayment()),直接写在抽象类里,所有子类立刻生效,无需逐个修改
看能力是否需要正交拼装、自由组合
当业务要求越来越灵活,不同模块要按需叠加能力,且这些能力彼此独立、不绑定具体类型,就该用接口。
- 例如订单今天要微信支付+短信通知,明天要支付宝+邮件+风控拦截,后天还要加汇率转换——这些能力互不依赖,可插可拔
- 一个类可以同时实现Payable、Notifiable、RiskCheckable,各能力之间零耦合
- 新加一种能力(如Refundable)时,只影响需要退款的类,其他类完全无感;Java 8+ 的 default 方法还能提供基础实现,避免重复编码
看是否要约束“谁可以拥有这个能力”,而不关心“它是什么”
当能力归属对象差异极大,甚至跨技术栈、跨团队,硬归为同一类会破坏语义,接口就是为此而生。
- 比如“支持双因素认证”这个能力,可能出现在 Web 页面、小程序、IoT 设备端——它们根本不是同类事物,但都必须满足同一契约
- 接口不问“你是什么”,只规定“你能做什么”,天然适合定义开放协议
- 微服务间通过 OpenAPI 定义接口、模块间用事件监听器接口通信,都是靠这种松耦合契约实现解耦
现实项目中常协同使用,而非二选一
大型系统往往既需要主干统一,又需要能力灵活。典型做法是抽象类搭骨架,接口填血肉。
- 比如AbstractOrderService封装下单主流程(校验→锁库存→生成单据→发消息),而把“库存锁定方式”抽成InventoryLockStrategy接口,由 RedisLock 或 DBLock 实现类动态注入
- Spring 中JdbcDaoSupport是抽象类,提供 JdbcTemplate 封装;PlatformTransactionManager是接口,适配 JDBC、JTA、Reactive 等多种事务机制
- 这种组合既保障了主干流程复用,又保留了横向能力扩展空间,也契合“面向接口编程”和“依赖倒置”原则











