接口定义“能做什么”以解耦协作,抽象类定义“怎么起步”以复用骨架;关键在于边界是否承载职责、隔离变化。

接口和抽象类在划清系统边界时作用不同:接口定义“能做什么”,用于解耦协作;抽象类定义“怎么起步”,用于复用骨架。关键不在选哪个,而在是否让边界真正承载职责、隔离变化。
用接口明确契约边界,聚焦能力声明
接口是系统间协作的“协议书”,应只回答“它能提供什么服务”,不掺杂实现细节或状态管理。
- 命名体现能力,如 PaymentProcessor(不是 PaymentService 或 AbstractPayment)
- 方法精简且语义一致,例如只含 process(PaymentRequest) 和 supports(PaymentMethod),不加 log()、retry() 或 validate()
- 避免在接口中暴露实现线索:不用 throws RedisConnectionException,不写 if (type == "alipay") 的判断逻辑
- 一个接口只表达一类职责,OrderRepository 不该同时管缓存刷新和风控校验
用抽象类封装共性流程,但不绑定具体角色
抽象类适合收敛多个子类重复的执行步骤,但它不该定义“我是谁”,而应说明“我们怎么一起走完这件事”。
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
- 把模板方法(如 executeWithRetry()、wrapInTransaction())放在抽象类里,但将可变环节(doActualWork())留给子类实现
- 字段仅保留流程必需的状态,比如 retryCount、timeoutMs,不放业务数据(如 orderId、userId)
- 构造器接受依赖接口(如 Clock、IdGenerator),而非具体实现,保证子类可替换
- 避免抽象类继承链过深——两层以内较安全,三层以上往往说明职责没切开
组合使用:接口定边界,抽象类减重复,实现类守契约
典型结构是:接口声明能力 → 抽象类提供通用执行框架 → 具体实现类专注领域逻辑。
立即学习“Java免费学习笔记(深入)”;
- 比如 NotificationSender 是接口,定义 send(Notice);AbstractHttpSender 封装 HTTP 调用、重试、超时;EmailSender 和 DingTalkSender 各自处理序列化与鉴权
- 当新增飞书通知时,只需写 FeishuSender,无需改接口、不碰抽象类,也不动其他实现
- 测试时,可直接 new MockNotificationSender() 注入,也可用 InMemorySender 验证流程,不依赖网络
警惕误用:不是所有“共性”都该抽成抽象类
如果两个类只是行为相似,但没有天然的“is-a”关系,强行用抽象父类反而制造耦合。
- Car 和 Bike 都能 start()、stop(),但它们不是同一类事物的变体,更适合共用 Movable 接口
- OrderService 和 UserService 都要记录日志,不该让它们继承 LoggableAbstract —— 日志是横切关注点,应通过 AOP 或独立 Logger 组件注入
- 工具方法(如日期格式化、JSON 解析)必须独立成类,不能塞进任何业务抽象类里

















