【问题标题】:Why do I actually need an interface in Spring Boot?为什么我实际上需要 Spring Boot 中的接口?
【发布时间】:2021-04-21 13:41:07
【问题描述】:

在 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; }
}

如果我不打算为“Adder”功能实现多个实现,为什么我实际上需要编写该接口?

该问题的一个常见答案是“您将需要它来模拟您的单元测试”。对此,我可以回答——不是真的。我可以使用您能想到的所有可能的模拟框架(例如 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);
}

说了这么多,IAadder接口能给我带来什么好处呢?

【问题讨论】:

  • @Stultuske 我确实听说过这个概念,但我试图在这里应用一些批判性思维并挑战我认为没有任何用处的范式。
  • 虽然几乎总是存在增量优势,但并不总是立即获得巨大优势,其中之一就是测试/DI/IoC/等。在 Spring Boot 中,接口用于代理生成——这并不意味着 everything 需要代理;这取决于它的使用方式(以及框架使用的方式)。不相关,但我将接口命名为Adder,并根据它们的详细信息命名实现。整个I 前缀在不久前就在Java 中死掉了。
  • “只要你有选择,JDK动态代理是首选”根据该链接。有人知道为什么会这样吗?
  • @DaveNewton "TESTING" 请全部大写。这使得使用 Spring 进行测试变得轻而易举。在编码了 15 年并偶尔在春季成为提交者之后,这是我的第一选择。
  • 虽然接口可能很有用,但我认为这个问题是在询问每个服务层类上的接口。

标签: java spring spring-boot unit-testing


【解决方案1】:

TBH 我讨厌人们盲目地遵循“每个服务都需要一个接口”的规则。这是维护的噩梦。

这种接口只在模块边界才有意义,接口本身驻留在consumer模块中,大喊‘嘿,这就是我需要的接口',而 provider 模块很乐意提供帮助。

在一个有凝聚力的模块中,除非您计划对同一功能进行多个实现,否则这样的接口完全没有意义。而且,由于您可以轻松地将类转换为接口,而无需客户抱怨(如果需要),我不明白为什么您应该过早地用额外的间接级别使代码库变得混乱。

【讨论】:

    【解决方案2】:

    我和同事也有过同样的对话。我觉得这个问题有点超出我的 Java 知识范围,但这里有一个镜头:

    我认为人们这样做有两个原因:

    1。 Java 动态代理

    Spring 尊重 @Transaction 等一些注解的方式是为类创建一个代理。

    根据the docs,Java 动态代理是基于 CGLIB 的代理的默认代理生成方法。 Java 动态代理只能与接口一起工作,因此人们养成了为每个服务创建接口的习惯。为什么首选 Java 动态代理?我不知道,tbh,但有几点:

    • CGLIB 曾经是一个你必须包含的独立库。现在它包含在 Spring 中
    • CGLIB 是一个第三方库,用于“破解”字节码,而不是 JDK 包含的动态代理
    • 我猜在某个时候constructors were invoked twice 使用CGLIB 时?也许还有其他怪癖?
    • 我在这里只有一点点经验,但我知道 CGLIB 有时会给出一些不透明的错误消息。

    我没有足够的经验来推荐使用全 CGLIB,只是为了删除冗余接口。

    2。文档鼓励的“软件工程原因”

    引用the docs:

    Spring AOP 也可以使用 CGLIB 代理。这是代理类而不是接口所必需的。默认情况下,如果业务对象未实现接口,则使用 CGLIB。由于对接口而不是类进行编程是一种很好的做法,因此业务类通常实现一个或多个业务接口。

    这里的文档间接地鼓励人们使用接口。在我看来,这似乎有点强硬,但这可能是它成为普遍做法的部分原因。一旦成为惯例,就会成为习惯。

    结论

    虽然我不同意所有具有单一实现的服务类都需要冗余接口的想法,但我同意服务类应被视为代码中的“外观点”,封装底层并提供干净的抽象到视图层。但我不明白为什么除非需要,否则他们需要有文字 java 接口。

    从结构的角度来看,这就是我的想法。但如果没有更多 CGLIB 与 JDK 代理的信息,我可能会坚持使用默认值。

    【讨论】:

      【解决方案3】:

      如果你没有为实现服务计划几个选项,那么理论上你可以不用接口。

      但是,如果您使用单元测试,事情就会复杂得多。在单元测试中并不总是可以使用有效的实现(例如,如果您正在使用数据库连接或与外部应用程序交互)。

      因此,最好使用界面。在您的问题中,您给出了最简单的情况,您可以在没有模拟对象的情况下进行操作。但这并不总是可行的。

      【讨论】:

      • It is not always possible to use a working implementation in unit tests - 为什么不呢?我可以在服务/组件后面封装我想要的任何东西(无论是数据库连接还是任何类型的外部接口),而无需接口超类。我错过了什么?
      猜你喜欢
      • 2011-04-01
      • 2012-03-16
      • 1970-01-01
      • 2010-09-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-08-06
      • 1970-01-01
      相关资源
      最近更新 更多