Dependency Injection Containers are Code Polluters

The following text is a partial translation of the original English article, performed by ChatGPT (gpt-3.5-turbo) and this Jekyll plugin:

尽管依赖注入(也称为“DI”)是面向对象编程中一种自然的对象组合技术(在Martin Fowler引入该术语之前就已经存在),但Spring IoC、Google Guice、Java EE6 CDI、Dagger等依赖注入框架将其变成了一种反模式。

我不打算讨论对“setter注入”(例如在Spring IoC中)和“field注入”(例如在PicoContainer中)的明显反对意见。这些机制简单地违反了面向对象编程的基本原则,并鼓励我们创建不完整、可变的对象,在应用程序执行过程中填充数据。请记住:理想的对象必须是不可变的,不能包含setter方法。

相反,让我们谈谈“构造函数注入”(例如在Google Guice中)及其与依赖注入容器的使用。我将尝试展示为什么我认为这些容器至少是多余的。

这就是依赖注入的含义(与简单的对象组合并无太大差异)。

对象 data 被称为 “依赖项”。

Budget 不知道它正在使用什么类型的数据库。它只需要从数据库中获取一个单元格的能力,通过方法 cell() 使用任意的 SQL 查询。例如,我们可以使用 PostgreSQL 实现的 DB 接口来实例化一个 Budget

换句话说,我们正在将一个依赖项“注入”到一个新的对象budget中。

与这种“依赖注入”的方法相比,另一种方法是让Budget决定它想要使用哪个数据库。

这很脏,导致了 1) 代码重复,2) 无法重用,和 3) 无法测试等等。不需要讨论为什么。这是显而易见的

因此,通过构造函数进行依赖注入是一种了不起的技术。嗯,其实不仅仅是一种技术。更像是 Java 和其他面向对象语言的一个特性。几乎任何对象都希望封装一些知识(也就是所谓的“状态”)。这就是构造函数的作用。

到目前为止一切都很好,但是接下来就是阴暗面——依赖注入容器。以下是它的工作原理(以Google Guice为例):

请注意:构造函数被注解为@Inject

然后,在应用程序启动时,我们应该在某个地方配置一个容器。

有些框架甚至允许我们通过 XML 文件配置注入器。

从现在开始,我们不再允许通过 new 运算符实例化 Budget,就像之前我们所做的那样。相反,我们应该使用刚刚创建的注入器。

注入自动发现,为了实例化Budget,它必须提供构造函数的参数。它将使用我们在注入器中实例化的Postgres类的实例。

这是使用Guice的正确和推荐方式。然而,还有一些更加黑暗的模式,虽然可能,但不推荐使用。例如,您可以将注入器设置为单例,并直接在Budget类中使用它。然而,即使是DI容器的制造商也认为这些机制是错误的。让我们忽略它们,专注于推荐的情况。

让我重申和总结一下关于依赖注入容器的“错误使用”场景:

  • Setter injection

  • 将注入器作为依赖项传递。

  • 将注射器设置为全局单例。

如果我们把它们全部放在一边不管,我们剩下的只有上面解释的构造函数注入。那么这有什么帮助呢?为什么我们需要它?为什么不能在应用程序的主类中使用普通的 new 呢?

我们创建的容器只是在代码库中添加了更多的代码行,甚至如果我们使用 XML,则还会添加更多的文件。它没有添加任何东西,除了额外的复杂性。如果我们有这个问题:“作为预算的参数使用了哪个数据库?”我们应该始终记住这一点。

现在,让我给您展示一个使用new构建应用程序的真实例子。这就是我们在rultor.com中创建一个”思考引擎”的方式(完整的类在Agents.java中)。

令人印象深刻吧?这是真正的对象组合。我相信这就是一个合适的面向对象应用程序的实例化方式。

至于依赖注入容器?我认为它们只是增加了不必要的干扰。

Translated by ChatGPT gpt-3.5-turbo/42 on 2023-12-15 at 06:58

sixnines availability badge   GitHub stars