Singletons Must Die

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

我认为单例模式作为反模式是显而易见的,因为有大量关于此的文章。然而,更常见的问题是如何在没有单例的情况下定义全局对象;对于我们中的许多人来说,这个答案并不明显。有几个例子:数据库连接池、仓库、配置映射等。它们似乎都是“全局”的;但我们该如何处理它们呢?

我假设您已经知道什么是单例以及为什么它是反模式。如果不知道,我建议您阅读这个 Stack Overflow 的帖子:单例模式有什么不好?

既然我们同意这是个坏主意,那么如果我们需要在应用程序的多个不同位置访问数据库连接池,我们该怎么办呢?我们只需要像这样:

在JAX-RS的REST方法中,稍后我们需要从数据库中检索一些东西。

如果你对JAX-RS不熟悉的话,它是一个简单的MVC架构,而这个text()方法就是一个”controller”。另外,我正在使用jcabi-jdbc中的简单JDBC封装器JdbcSession

我们需要将Database.INSTANCE作为单例对象,对吗?我们需要它在全局范围内可用,以便任何 MVC controller 可以直接访问它。由于我们都理解并同意单例是一件邪恶的事情,那么我们用什么来替代它呢?

依赖注入就是答案。

我们需要将这个数据库连接池作为控制器的依赖项,并确保通过构造函数提供它。然而,在这种特定情况下,对于JAX-RS,我们无法通过构造函数来实现,因为它的架构很丑陋。但我们可以创建一个ServletContextListener,在其contextInitialized()方法中实例化一个Database对象,并将该实例作为servletContext的属性添加进去。然后,在控制器中,我们通过给一个setter方法添加javax.ws.rs.core.Context注解,并在其上使用getAttribute()来获取servlet上下文。这是非常糟糕和过程化的,但比单例要好。

一个合适的面向对象设计应该通过构造函数将Database的实例传递给所有可能需要它的对象。

然而,如果有很多依赖项怎么办?难道我们要创建一个包含10个参数的构造函数吗?不,我们不这样做。如果我们的对象确实需要10个依赖项来完成工作,我们需要将它们拆分成更小的部分。

就是这样。忘记单例,永远不要使用它们。将它们转换为依赖项,并通过new操作符从一个对象传递到另一个对象。

Translated by ChatGPT gpt-3.5-turbo/42 on 2023-11-17 at 14:46

sixnines availability badge   GitHub stars