不能直接 new 实现类,因为会导致业务逻辑与具体服务商强绑定,难以替换实现或进行单元测试;应面向接口编程,通过依赖注入解耦,让调用方只依赖契约而非具体实现。

为什么不能直接 new 实现类?
因为 new AliyunSmsSender() 这种写法,会让业务逻辑和具体服务商强绑定。一旦要切到腾讯云或加个 Mock 发送器,就得全局搜索、逐个替换构造语句——上线前改代码,错一行就发不出短信。
面向接口编程不是为了“看起来规范”,而是让调用方只认契约,不认人:
-
SmsSender接口只声明send(String phone, String content),不管背后是 HTTP 调用、本地日志打印还是异步队列 - 订单服务里只写
private final SmsSender sender;,构造时由外部注入,自己完全不操心谁来实现 - Spring 的
@Autowired能生效,正是因为它是按类型(SmsSender.class)找 Bean,而不是按AliyunSmsSender.class
接口该定义什么?不该定义什么?
接口是「能做什么」的契约,不是「怎么配置」的说明书。常见错误是把实现细节塞进接口,比如加 setRetryTimes(int) 或 enableMockMode() —— 这会导致所有实现类被迫支持,哪怕 Redis 实现根本不需要重试。
正确做法:
立即学习“Java免费学习笔记(深入)”;
- 方法名聚焦业务动作:
process(PaymentRequest)、validate(Order)、findActiveUsers() - 避免暴露策略参数;重试、超时、开关等应封装在实现类内部,或通过配置中心/环境变量控制
- 不为单实现硬抽接口:像
LocalDateTimeUtils这种工具类,加接口反而增加跳转成本,毫无必要
Spring 里怎么真正用好接口?
光有接口没用,关键得让框架按接口装配。Spring 默认 @Autowired 是 byType 匹配,所以必须确保容器中只有一个 SmsSender 类型的 Bean,否则会报 NoUniqueBeanDefinitionException。
实操要点:
- 用
@Service标记实现类,并让它implements SmsSender,不要在字段上写@Autowired private AliyunSmsSender - 多实现共存时,用
@Primary指定默认,或用@Qualifier("tencentSmsSender")显式指定 - 测试时可直接 new 一个
MockSmsSender()注入,不用启动容器,也不用改任何业务代码
什么时候可以不面向接口?
接口不是银弹。硬套会制造噪音,比如:
- DTO、VO、枚举这些纯数据载体,没有行为,不需多态,加接口纯属冗余
- 确定永远只有一种实现且无测试 mock 需求的组件(如
JsonUtil),接口只会让调用链变长 - 刚起步的小项目,连第一个外部依赖都没接入,先写死
new ArrayList()完全没问题;等真要换LinkedList或加缓存逻辑时,再抽List接口更自然
真正需要接口的地方,是那些「可能被替换」或「必须被模拟」的边界:远程调用适配器、策略分支、仓储层、事件处理器——这些地方的接口,不是设计出来的,是演进逼出来的。










