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

接口不是为了多写几行代码,而是为了替换时不用改调用方
面向接口编程的核心判断就一条:只要某段逻辑未来可能换实现(比如从本地缓存换成 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(只管记日志),三者通过组合而非继承协作。
立即学习“Java免费学习笔记(深入)”;
- 接口方法数建议 ≤ 3 个,单个方法参数 ≤ 2 个;超了就考虑是否该拆
- 避免在接口里定义常量(如
MAX_RETRY = 3),那是实现细节,应由具体类自己管 - Java 8+ 可以用
default方法补救小范围共性逻辑,但别用来掩盖设计缺陷
Mock 测试失败?大概率是接口暴露了不该暴露的内部状态
写单元测试时发现 messageSender.send(msg) 总是抛 NullPointerException,或者 mock 后行为不对——十有八九是接口方法悄悄依赖了某个未注入的字段(比如 private Config config),而这个字段在 mock 对象里是 null。
性能影响:接口本身零开销,但若接口方法里做了同步锁、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显式选,避免漏配











