单例模式必亡

原文: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操作符来使他们在各个对象之间传递。

本文为博主原创文章,转载请在明显位置注明出处: http://www.cnblogs.com/sweng

本作品采用知识共享署名-非商业性使用-禁止演绎 3.0 未本地化版本许可协议进行许可。

原文地址:https://www.cnblogs.com/sweng/p/5655289.html