在spring/spring boot中编程时,通常的做法是让每个服务/组件实现一个接口,例如:
public interface IAdder {
int add(int a, int b);
}
@Service
public class Adder implements IAdder {
public int add(int a, int b) { return a + b; }
}
如果我不打算为“加法器”功能实现多个实现,为什么我真的需要编写这个接口呢?
对于这个问题,一个常见的答案是“在单元测试中进行模拟时需要它”。对此,我可以回答-不是真的。我可以使用您能想到的所有可能的模拟框架来模拟加法器(例如mockito):
@Autowired
IMultiplier multiplier; // Class under test
@Mock
IAdder adder;
@Test // Assuming I implement multiplication by multiple additions
public void multiplicationTest() {
// Arrange
when(adder).add(3, 3).thenReturn(6);
// Act
int result = multiplier.multiply(3, 5);
// Assert
verify(adder, times(4)).add(3, 3);
assertThat(result).isEqualTo(15);
}
话虽如此,iAder接口能为我提供什么优势?
3条答案
按热度按时间6mzjoqzu1#
我和同事也有过同样的谈话。我觉得这个问题有点超出了我的java知识范围,但这里有一个镜头:
我认为人们这样做有两个原因:
1. java动态代理
spring接受@transaction等注解的方式是为类创建一个代理。
根据文档,java动态代理是基于cglib的代理的默认代理生成方法。java动态代理只能与接口一起工作,因此人们养成了为每个服务创建接口的习惯。为什么首选java动态代理?我不知道,tbh,但有几点:
cglib曾经是一个单独的库,你必须包含它。现在它包括在Spring中
cglib是一个第三方库,用于“破解”字节码,而jdk包含了动态代理
我猜在使用cglib的时候,构造函数被调用了两次?也许还有别的怪癖?
我在这里只有一点点经验,但我知道cglib有时会给出一些不透明的错误消息。
我没有足够的经验来推荐使用完整的cglib仅仅是为了删除冗余接口。
2. "软件工程的理由”,鼓励的文件
引用文档:
springaop也可以使用cglib代理。这是代理类而不是接口所必需的。默认情况下,如果业务对象没有实现接口,则使用cglib。由于编程到接口而不是类是一种很好的实践,业务类通常实现一个或多个业务接口。
这里的文档间接地鼓励人们使用接口。在我看来,这似乎有点严厉,但这可能是为什么它已成为普遍做法的一部分。一旦习惯了,就变成了习惯。
结论
虽然我不同意所有具有单个实现的服务类都需要冗余接口的观点,但我同意应该将服务类视为代码中的“facade point”,封装底层并为视图层提供干净的抽象。但我不明白,除非有需要,否则它们为什么需要有字面的java接口。
这是我从结构的Angular 所想的。但是如果没有更多的cglib和jdk代理信息,我可能会坚持默认设置。
hmtdttj42#
如果您没有为实现服务计划几个选项,那么理论上您可以不使用接口。
但是,如果使用单元测试,事情就复杂得多。在单元测试中使用工作实现并不总是可能的(例如,如果您正在使用数据库连接或与外部应用程序交互)。
因此,最好使用接口。在您的问题中,您给出了不使用模拟对象的最简单情况。但这并不总是可能的。
ki0zmccv3#
我讨厌人们盲目地遵循“每个服务都需要一个接口”的规则。这是维护的噩梦。
这种接口只有在模块边界上才有意义,接口本身就驻留在使用者模块中,大声喊着“嘿,这是我需要的接口”,提供者模块很乐意这样做。
在一个内聚模块中,除非您计划使用同一功能的多个实现,否则这样的接口毫无意义。而且,由于您可以轻松地将一个类转换为一个接口,而无需客户机抱怨(如果需要的话),我不明白为什么您应该过早地用额外的间接层来混乱您的代码库。