29

我到处阅读有关 Spring 如何鼓励您在代码中使用接口的信息。我没看到。spring xml 配置中没有接口的概念。Spring 的哪一部分实际上鼓励您使用接口(除了文档)?

4

9 回答 9

33

依赖倒置原则很好地解释了这一点。特别是图4。

A. 高级模块不应该依赖于低级模块。两者都应该依赖于抽象。

B. 抽象不应依赖于细节。细节应该取决于抽象。

将上面链接中的示例翻译成java:

public class Copy {
    private Keyboard keyboard = new Keyboard(); // concrete dependency
    private Printer printer = new Printer();    // concrete dependency
    public void copy() {
        for (int c = keyboard.read(); c != KeyBoard.EOF) {
            printer.print(c);
        }
    }
}

现在依赖反转:

public class Copy {
     private Reader reader; // any dependency satisfying the reader interface will work
     private Writer writer; // any dependency satisfying the writer interface will work
     public void copy() {
        for (int c = reader.read(); c != Reader.EOF) {
            writer.write(c);
        }
     }
     public Copy(Reader reader, Writer writer) {
         this.reader = reader;
         this.writer = writer;
     }
}

现在Copy支持的不仅仅是从键盘复制到打印机。

它能够从任何复制Reader到任何复制,Writer而无需对其代码进行任何修改。

现在有了春天:

<bean id="copy" class="Copy">
    <constructor-arg ref="reader" />
    <constructor-arg ref="writer" />
</bean>

<bean id="reader" class="KeyboardReader" />
<bean id="writer" class="PrinterWriter" />

也许:

<bean id="reader" class="RemoteDeviceReader" />
<bean id="writer" class="DatabaseWriter" />
于 2008-11-02T00:45:26.810 回答
33

当你为你的类定义一个接口时,它有助于依赖注入。您的 Spring 配置文件本身没有关于接口的任何内容——您只需输入类的名称。

但是如果你想注入另一个提供“等效”功能的类,使用接口真的很有帮助。

例如,假设您有一个分析网站内容的类,并且您正在使用 Spring 注入它。如果您将其注入的类知道实际的类是什么,那么为了将其更改,您将不得不更改大量代码以使用不同的具体类。但是,如果您创建了一个Analyzer界面,您可以轻松地注入您的原始界面,DefaultAnalyzer就像您可以模拟一个模型DummyAnalyzer或什至另一个本质上做相同事情的界面一样,例如 aPageByPageAnalyzer或其他任何东西。为了使用其中之一,您只需更改您在 Spring 配置文件中注入的类名,而不是通过代码更改类。

我花了大约一个半的时间才真正开始看到它的用处。就像大多数最终有用的东西(在企业语言中)一样,起初它似乎是毫无意义的工作,直到您的项目开始增长,然后您发现通过预先做更多的工作节省了多少时间。

于 2008-11-02T05:04:31.153 回答
12

这里的大多数答案都是某种形式的“您可以轻松换出实现”,但我认为他们无法回答的是为什么?部分。对此,我认为答案几乎是明确的可测试性。无论您是否使用 Spring 或任何其他 IOC 框架,使用 Dependency Injection 都会使您的代码更容易测试。在使用 writer 而不是 PrinterWriter 的情况下,您可以在单元测试中模拟 Writer 接口,并确保您的代码以您期望的方式调用它。如果您直接依赖于类实现,您唯一的选择就是走到打印机前检查它,这不是很自动化。此外,如果您依赖于类调用的结果,则无法模拟它可能会阻止您访问测试中的所有代码路径,从而降低它们的质量(潜在地)简单地说,您应该将对象图的创建与应用程序逻辑分离。这样做会使您的代码更容易测试。

于 2008-11-03T23:42:50.053 回答
6

还没有人提到在很多情况下不需要创建一个接口以便实现类可以快速切换,因为不会有多个实现类。

当不需要创建接口时,类将成对创建(接口加实现),添加不必要的样板接口并造成潜在的依赖混淆,因为在 XML 配置文件中,组件有时会被其接口引用,有时被其实现引用,与在运行时没有后果,但在代码约定方面不连贯。

于 2014-07-09T14:09:04.733 回答
2

您可能想尝试自己使用它以便更好地看到这一点,从文档中可能不清楚 Spring 如何鼓励接口使用。

这里有几个例子:

  1. 假设您正在编写一个需要从资源(例如,文件)中读取的类,该资源可能以多种方式(例如,在类路径、绝对文件路径、作为 URL 等)中引用。你想org.springframework.core.io.Resource在你的类上定义一个(接口)属性。然后在您的 Spring 配置文件中,您只需选择实际的实现类(例如org.springframework.core.io.ClassPathResource,、org.springframework.core.io.FileSystemResourceorg.springframework.core.io.UrlResource)。Spring 基本上是作为一个非常通用的工厂运行的。

  2. 如果您想利用 Spring 的 AOP 集成(例如添加事务拦截器),您几乎需要定义接口。您在 Spring 配置文件中定义拦截点,Spring 会根据您的接口为您生成代理。

这些都是我亲身经历过的例子。我敢肯定还有更多。

于 2008-11-01T23:50:03.147 回答
2

从接口生成代理很容易。

如果您查看任何 spring 应用程序,您将看到服务和持久性接口。使 spring 成语确实鼓励使用接口。没有比这更明确的了。

于 2008-11-14T00:13:05.820 回答
2

编写单独的接口会增加通常不必要的复杂性和样板代码。它还使调试变得更加困难,因为当您在 IDE 中单击方法调用时,它会显示接口而不是实现。除非您在运行时交换实现,否则无需走这条路。

像 Mockito 这样的工具使使用依赖注入测试代码变得非常容易,而无需在接口上堆积。

于 2018-08-14T20:53:44.523 回答
1

Spring 不会强迫你在任何地方使用接口,它只是一个很好的实践。如果你有一个 bean 有一些属性是接口而不是具体的类,那么你可以简单地用实现相同接口的模型切换一些对象,这对于某些测试用例很有用。

如果您使用例如 Hibernate 支持类,您可以为您的 DAO 定义一个接口,然后单独实现它;拥有接口的优点是您将能够使用 Spring 拦截器对其进行配置,这将允许您简化代码;您不必编写任何代码覆盖 HibernateExceptions 并在 finally 段中关闭会话,也不必以编程方式定义任何事务,您只需使用 Spring 以声明方式配置所有这些东西。

当您编写快速而肮脏的应用程序时,您可以使用 JDBC 或一些您最终不会在最终版本中使用的简单框架来实现一些简单的 DAO;如果它们实现了一些通用接口,您将能够轻松地切换这些组件。

于 2008-11-04T19:31:49.297 回答
1

如果您不使用接口,您将面临自动装配失败的风险:有时 Spring 会为 Bean 创建一个代理类。这个 Proxy 类不是服务实现的子类,但它重新实现了它的所有接口。Spring 将尝试自动装配此 Bean 的实例,但是此 Proxy 类与 Bean 类不兼容。所以用 Bean 类声明一个字段会导致“不安全的字段赋值”异常。

您无法合理地知道 Spring 何时代理服务(您也不应该),因此为了保护自己免受这些意外的影响,最好的做法是声明一个接口并在声明自动装配字段时使用此接口。

于 2015-05-19T13:53:47.003 回答