推荐用 PaymentService 接口而非 CreditCardPayment 实现类声明变量,因接口解耦便于替换实现、支持模拟测试、利于IDE导航且符合Spring依赖注入原则;接口应仅在需多实现或可替换处定义,避免泛滥;切换实现应通过配置驱动(如@Profile或反射加载),而非硬编码;接口方法须聚焦行为契约,封装可变参数,慎用默认方法。
PaymentService 而不是 CreditCardPayment 声明变量因为调用方一旦写死具体实现类,就锁死了后续替换路径。比如所有地方都写了 new AliyunSmsSender(),哪天要切腾讯云,就得全局搜、逐个改、反复测——上线前夜改代码,错一个就发不出验证码。
而声明为接口类型,如 private SmsSender smsSender,构造或注入时才决定用哪个实现,业务逻辑里完全不感知底层是谁在发短信。
@Autowired 默认按接口类型注入,不是按实现类名;写 @Autowired private UserServiceImpl 会破坏可扩展性,且多实现时直接报错Mockito.mock(SmsSender.class) 替换真实调用,不用启短信网关接口不是越多越好。它只应在「可能有多种实现」或「需要被模拟/替换」的位置存在。
OrderRepository)、外部服务适配(SmsC
lient)、策略分支(PricingStrategy)StringUtils)、DTO 对象、确定永不变的本地逻辑(如 LocalDateTimeUtils)UserServiceImpl 单独配一个 UserServiceImplInterface,纯属自找麻烦硬编码 if (env == "prod") new AliyunSmsSender() else new MockSmsSender() 仍是耦合,只是把 new 换成了 if。真正解耦是让实现类名从外部来。
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()),否则别为了“省事”在接口里加默认实现,那会污染契约