接口本质是为替换实现而不改调用方,核心判断是“未来可能换实现就抽成接口”,需避免硬编码、保持方法签名稳定、依赖倒置、窄接口设计、组合优于继承、控制方法与参数数量、隔离实现细节、确保无状态及依赖注入。

接口不是为了多写几行代码,而是为了替换时不用改调用方
面向接口编程的核心判断就一条:只要某段逻辑未来可能换实现(比如从本地缓存换成 Redis,从发邮件换成发短信),它就得抽成接口。不是“看起来高大上才用”,是“改起来不牵连别处才必须用”。
常见错误现象:new SmsService() 直接出现在业务方法里;或者 if (type.equals("email")) { new EmailSender() } else { new SmsSender() } 这种硬编码分支。
- 所有具体实现类(如
EmailSender、SmsSender)必须只依赖接口(如MessageSender),不能反向依赖 - 接口方法签名要稳定——别轻易加参数、改返回值,否则所有实现类全得动
- Spring 项目中,优先用
@Autowired注入接口类型,而不是具体类名;没用 Spring 的,靠工厂或构造函数传入
接口定义要窄,别把“能想到的所有功能”都塞进去
一个接口如果包含 send()、retry()、logFailure()、getProviderName(),大概率说明它在承担多个职责。解耦失败的典型表现就是:新加一种发送方式,结果发现得重写日志逻辑、重写重试策略。
使用场景:比如消息发送,拆成 MessageSender(只管发)、RetryPolicy(只管重试)、DeliveryLog(只管记日志),三者通过组合而非继承协作。
- 接口方法数建议 ≤ 3 个,单个方法参数 ≤ 2 个;超了就考虑是否该拆
- 避免在接口里定义常量(如
MAX_RETRY = 3),那是实现细节,应由具体类自己管 - Java 8+ 可以用
default方法补救小范围共性逻辑,但别用来掩盖设计缺陷
Mock 测试失败?大概率是接口暴露了不该暴露的内部状态
写单元测试时发现 messageSender.send(msg) 总是抛 NullPointerException,或者 mock 后行为不对——十有八九是接口方法悄悄依赖了某个未注入的字段(比如 private Config config),而这个字段在 mock 对象里是 null。
分析 Java Spring Boot 后端 Controller 代码,自动生成 Markdown 格式的接口文档。当用户说“生成 Java 接口文档”、“生成 API 文档”、“从 Spring 代码生成文档”时触发。
性能影响:接口本身零开销,但若接口方法里做了同步锁、IO 或复杂计算,又没文档说明,调用方就容易误用。
- 接口方法必须是无状态的,或状态完全由入参决定;禁止读取静态变量、全局配置、线程局部变量
- 所有外部依赖(如
HttpClient、RedisTemplate)必须通过构造函数或 setter 注入,不能在接口方法里 new 出来 - 测试时用
Mockito.mock(MessageSender.class)就够,别 mock 具体实现类——mock 实现类说明你已经耦合了
Spring Boot 中 @Qualifier 不生效?检查是不是漏了 @Component
当你写了两个 MessageSender 实现类,又用 @Qualifier("smsSender") 指定,却报 NoUniqueBeanDefinitionException,问题往往不在注解本身,而在实现类没被 Spring 扫描到。
常见错误路径:com.example.sender.SmsSender 在启动类同包下,但 com.example.infra.SmsSenderImpl 在其他包,且没加 @Component 或没被 @ComponentScan 覆盖。
- 每个实现类必须显式标注
@Component(或@Service),不能只靠接口存在 -
@Qualifier的字符串值必须和@Component("smsSender")里的名字严格一致,大小写敏感 - 更稳妥的做法是用
@Primary标一个默认实现,其余用@Qualifier显式选,避免漏配
接口真正的复杂点不在语法,而在于划界——哪个逻辑归这里,哪个该甩出去。划错了,后面所有人改起来都像在拆炸弹。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










