接口管能力组合与解耦边界,抽象类管类族内聚与逻辑复用;接口定义横向切面契约(如paymentprocessor),抽象类提供纵向骨架(如httpservlet);混用时须职责清晰,避免污染契约或违背语义。

接口和抽象类在架构层面不是“选哪个更好”,而是“承担不同角色”——接口管能力组合与解耦边界,抽象类管类族内聚与逻辑复用。用错位置会直接卡死扩展路径。
接口定义的是系统能力的“横向切面”
一个服务模块对外暴露的能力(如 PaymentProcessor、NotificationSender、RetryPolicy),天然适合用接口表达。它不绑定实现细节,也不要求调用方知道背后是 Spring Cloud Stream 还是 Kafka,只承诺“能发通知”“能重试”“能扣款”。
- 多个不相关的类可以共用同一接口:比如
OrderService和RefundService都实现Transactional接口,但它们没有继承关系 - Spring 的
@Autowired注入点几乎全是接口:这样才方便用@MockBean或替换为LoggingPaymentProcessor等装饰器 - 接口一旦发布,修改成本极高:加一个新方法,所有实现类都要补实现(除非用
default,但仅限极简逻辑)
抽象类定义的是类族内部的“纵向骨架”
当你发现 HttpServlet、AbstractList、JpaSpecificationExecutor 这些类都在干同一件事:把重复的初始化、校验、模板流程收上来,把变化点(doGet()、get(int)、toPredicate())留给子类填空——这就是抽象类的典型场景。
- 抽象类可含字段、构造器、
protected方法:比如AbstractEntityRepository里存JdbcTemplate实例,子类直接用 - 子类必须单继承:如果已有
extends BaseJpaRepository,就再也不能继承别的模板类,这是硬约束 - 抽象类改了非抽象方法,所有子类自动获得更新;但改了抽象方法签名,所有子类立刻编译失败
当两者混用时,关键看“谁决定扩展方向”
现代 Java 架构中,接口和抽象类常协同出现,但职责必须清晰:
-
Repository<t id></t>是接口:定义“能查、能删、能存”的契约,允许JpaRepository、MongoRepository、MyBatisRepository并行实现 -
SimpleJpaRepository是抽象类的具体子类(实际是 final 类,但它的父类QuerydslJpaRepository等有抽象层):封装了通用 SQL 拼装、分页处理等逻辑,避免每个 JPA 实现都重写一遍 - 如果你试图在
Repository接口里塞default void logAccess() { ... }来统一打日志,就违背了接口语义——日志是横切关注点,该用 AOP 或装饰器,而不是污染契约
最容易被忽略的一点:抽象类能带状态,接口不能。哪怕你只在接口里加一个 public static final String VERSION = "v2",它就不再是纯粹的行为契约,而开始泄露实现意图——这时候该考虑是不是该拆出一个 Versioned 接口,或把版本控制下沉到抽象基类里。










