原文:http://www.yegor256.com/2016/06/27/singletons-must-die.html
因为有很多关于单例模式正在成为一个反模式的文章,我认为把单例模式说成是一个反模式是显而易见的。总之,问题往往是如何不用单例模式来定义一个全局变量,而这个问题的答案对许多人来说并不知道。有很多例子:一个数据库连接池,一个数据访问组件,一个配置文件等等。他们自然看成一个全局变量,但是我们怎么写?
我相信你已经知道什么是单例模式并且知道为什么它是一个反模式。如果你不知道,我推荐你去读StackOverflow的这篇:为什么单例模式这么糟糕?
既然我们同意这个糟糕的模式,那如果我们需要应用用不同的方式连接数据库连接池,我们怎么办?我们需要这样:
class Database { public static Database INSTANCE = new Database(); private Database() { // create a connection pool } public java.sql.Connection connect() { // Get new connection from the pool // and return } }
之后,我们来看JAX-RS REST方法,我们需要从数据库中取出一些东西:
@Path("/") class Index { @GET public String text() { java.sql.Connection connection = Database.INSTANCE.connect(); return new JdbcSession(connection) .sql("SELECT text FROM table") .fetch(new SingleOutcome(String.class)) } }
这个例子中你可能对JAX-RS不熟悉,这是一个简单的MVC架构,这个text()方法是一个控制器,另外,我使用的SqlSession是jcabi-jdbc中的一个简单的JDBC封装。
我们需要Database.INSTANCE成为一个单例,对吗?我们需要它成为全局这样才能任何MVC的控制器可以直接调用它。既然我们都理解并同意单例模式是一个糟糕的东西,我们拿什么来替换它?
答案是一个依赖注入:
我们需要让这个数据库连接池依赖控制器,并且保证它又构造器提供。总之,在这个特别的例子里,对于JAX-RS,幸亏它丑陋的架构,我们没有通过构造器来构造。但是我们能够在它contextInitialized()方法中创建ServletContextListener来实例化一个Database。之后,在控制器中,我们通过添加javax.ws.rs.core.Context注释在setter并用getAttribute()方法取回servlet文本内容。这绝对是糟糕死板的,但是这比单例模式好。
一个合适的面相对象设计会传送一个Database实例给所有需要在构造器中使用的对象。
无论如何,如果有很多依赖我们怎么办?我们要写一个有10个参数的构造器吗?不,我们不需要。如果我们对象在运行时真的有10个依赖,我们需要把它们切割成更小的对象。
就是这样,忘记单例模式,不要再使用它。把它们转化成依赖,并且使用new操作符来使他们在各个对象之间传递。