推荐用 PaymentService 接口而非 CreditCardPayment 实现类声明变量,因接口解耦便于替换实现、支持模拟测试、利于IDE导航且符合Spring依赖注入原则;接口应仅在需多实现或可替换处定义,避免泛滥;切换实现应通过配置驱动(如@Profile或反射加载),而非硬编码;接口方法须聚焦行为契约,封装可变参数,慎用默认方法。

为什么推荐用 PaymentService 而不是 CreditCardPayment 声明变量
因为调用方一旦写死具体实现类,就锁死了后续替换路径。比如所有地方都写了 new AliyunSmsSender(),哪天要切腾讯云,就得全局搜、逐个改、反复测——上线前夜改代码,错一个就发不出验证码。
而声明为接口类型,如 private SmsSender smsSender,构造或注入时才决定用哪个实现,业务逻辑里完全不感知底层是谁在发短信。
- Spring 的
@Autowired默认按接口类型注入,不是按实现类名;写@Autowired private UserServiceImpl会破坏可扩展性,且多实现时直接报错 - 测试时可直接用
Mockito.mock(SmsSender.class)替换真实调用,不用启短信网关 - IDE 跳转时看到的是契约(方法签名),不是某家厂商的 SDK 细节,阅读成本更低
接口该定义在哪?哪些类不该硬加接口
接口不是越多越好。它只应在「可能有多种实现」或「需要被模拟/替换」的位置存在。
- 适合抽接口的:数据访问层(
OrderRepository)、外部服务适配(SmsClient)、策略分支(PricingStrategy) - 没必要抽接口的:工具类(
StringUtils)、DTO 对象、确定永不变的本地逻辑(如LocalDateTimeUtils) - 警惕“接口泛滥”:为
UserServiceImpl单独配一个UserServiceImplInterface,纯属自找麻烦
怎么让切换实现不改代码?配置驱动是关键
硬编码 if (env == "prod") new AliyunSmsSender() else new MockSmsSender() 仍是耦合,只是把 new 换成了 if。真正解耦是让实现类名从外部来。
立即学习“Java免费学习笔记(深入)”;
- Spring Boot 中可配
spring.profiles.active=aliyun,再用@Profile("aliyun") @Bean控制注入 - 更灵活的做法:把实现类全限定名写进配置项,如
sms.implementation=com.example.TencentSmsSender,启动时用反射加载 - 注意:反射加载需校验类是否真的实现了接口,否则运行时报
ClassCastException
接口方法设计最容易踩的坑是什么
接口不是功能清单,它是行为契约。塞太多配置细节进去,等于把实现策略强加给所有实现者。
- 错误示范:
void send(String content, int retryTimes, boolean useSSL)—— 这些参数属于实现内部策略,不该暴露在接口上 - 正确方向:
void send(SmsRequest request),把可变参数封装进SmsRequest对象,实现类各自解析 - 避免滥用默认方法:除非是领域共识逻辑(如
Collection.isEmpty()),否则别为了“省事”在接口里加默认实现,那会污染契约











