面向接口编程需做到接口窄而专、依赖通过构造器注入、实现可无缝替换。接口只定义单一职责契约,不包含常量与实现细节;业务类不直接new实现类或用if-else判断类型;方法须无状态、可预测,所有外部依赖必须显式注入。

面向接口编程不是加个 interface 就算完成,关键在于让业务模块只认“契约”,不认“谁来履约”。解耦效果好不好,取决于接口怎么定义、依赖怎么传、实现怎么换——三者缺一不可。
接口要窄而专,一个只管一件事
接口不是功能清单,而是职责契约。方法太多、参数太杂,就等于把多个责任绑在一起,后续改起来牵一发而动全身。
- 比如发消息,就定义
MessageSender,只留send(Message msg)一个方法 - 重试逻辑单独抽成
RetryPolicy,失败日志单独抽成DeliveryLog - 别把
send()、retry()、logFailure()塞进同一个接口 - 接口里不放常量(如
MAX_RETRY = 3),那是实现类该管的事
业务类只持接口,不碰 new 或 if-else 创建
业务代码一旦自己 new 实现类,或者用字符串判断类型再创建对象,解耦就失效了。
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
- 正确做法:在构造器中接收接口类型,例如
public OrderService(NotificationService service) - 没 Spring 时,可用工厂类统一创建,但调用方仍只依赖接口,不依赖工厂返回的具体类型
- 禁止出现
new EmailService()或if ("sms".equals(type)) { new SMSService() }
实现可替换,业务代码零修改
只要新旧实现类都遵守同一接口,运行时切换就不动一行业务逻辑。
- 订单服务依赖
UserRepository,当前用JdbcUserRepository,明天换成CacheUserRepository(内部包装 JDBC),UserService完全无感 - 事件发布依赖
EventPublisher,背后是 Kafka 还是内存队列,业务模块不需要知道 - 单元测试直接传入
Mockito.mock(MessageSender.class),验证是否调用了send,而不是关心它怎么发
接口方法必须无状态、可预测、易注入
如果一个接口方法的行为受配置、静态变量或未注入字段影响,那它就不是纯契约,测试和替换都会出问题。
- 所有外部依赖(如
HttpClient、RedisTemplate)必须通过构造器注入,不能在方法里偷偷 new 或静态调用 - 方法行为应完全由入参决定;若需异步、锁或耗时操作,应在文档中明确说明,避免调用方误用
- 测试中出现
NullPointerException,大概率是接口方法里用了未注入的私有字段(如private Config config)
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










