单例是一种常见的设计模式,但是实际开发中,有些人会认为单例是一种反模式

那么为什么单例是一种反模式

不用单例的话,如何解决呢?

单例存在的问题

我们使用单例,大多是为了表示一个全局唯一的类,比如配置类,ID生成器,连接池类,因为书写简单,方便调用,可以直接使用,是非常的好处,但是这种方式,就像是硬编码一样,也难以变通

1.单例的出现导致面向对象特性不好

面向对象的特性是封装 继承 多态 抽象,但是单例式除了支持了封装,对于继承 抽象  多态,都或多或少有影响,为什么这么说

还是拿一个IdGenerator来说明

在上面,我们直接获取到id,但是如果有一天,我们希望针对不同的业务采用不同的ID生成算法,比如订单ID和用户ID采用不同的ID生成器,那么会比较复杂,我们需要修改所有用到IdGenerator的地方

而且对于多态 继承的实现也不好,即使能做到,也会导致可读性变差,所以,一旦设计为了单例类,就意味着放弃了继承和多态两个特性

2.会隐藏类和类之间的依赖关系

在阅读代码的时候,希望能够一眼看出类和类之间的依赖关系,搞清楚依赖什么类,但是由于单例类的特性,不需要创建,不需要依赖参数传递,直接调用就可以了,导致在函数实现复杂的的时候,阅读起来,需要仔细的阅读每一个单例类的代码实现

3.对于扩展性不好

只能有一个对象实力,如果某一天,我们需要创建多个实例,需要改动的地方太大了,比如说,我们有一个数据库连接池,方便我们操控数据库连接资源,于是我们将其设置为了,但是实际上单例类使用起来,发现有些SQL语句运行的慢,有的SQL运行的快,我们希望能够分块数据库连接池,避免慢的SQL语句污染了其他语句执行速度,所以就不能设计为单例类

4.单例对代码的可测试性不好

如果单例类 依赖于比较重的外部资源,比如DB,那么这样的单元测试,就不好通过mock的方式进行替换

如果一个单例类包含了全局变量,例如id,那么每一次的操作都会修改全局变量,那么就得在测试的时候小心,不同测试用例之间的影响问题

5.单例不支持有参数的构造函数

那么有时候我们需要设置一些大小的时候,就很不方便

如果设置一个init()函数,先进行init()设置参数,再调用getInstance()方法,那么会很麻烦

public class Singleton {

private static Singleton instance = null;

private final int paramA;

private final int paramB;

private Singleton(int paramA, int paramB) {

this.paramA = paramA;

this.paramB = paramB;

}

public static Singleton getInstance() {

if (instance == null) {

throw new RuntimeException(“Run init() first.”);

}

return instance;

}

public synchronized static Singleton init(int paramA, int paramB) {

if (instance != null){

throw new RuntimeException(“Singleton has been created!”);

}

instance = new Singleton(paramA, paramB);

return instance;

}

}

Singleton.init(10, 50); // 先init,再使用

Singleton singleton = Singleton.getInstance();

而直接将参数放到getInstance()当中,也很麻烦

public class Singleton {

private static Singleton instance = null;

private final int paramA;

private final int paramB;

private Singleton(int paramA, int paramB) {

this.paramA = paramA;

this.paramB = paramB;

}

public synchronized static Singleton getInstance(int paramA, int paramB) {

if (instance == null) {

instance = new Singleton(paramA, paramB);

}

return instance;

}

}

Singleton singleton = Singleton.getInstance(10, 50);

而且会导致第二次的设置其实一点用处都没有

第三种思路,将参数设为一个全局变量,可以去读取,但是操控性并不好

public class Config {

public static final int PARAM_A = 123;

public static fianl int PARAM_B = 245;

}

public class Singleton {

private static Singleton instance = null;

private final int paramA;

private final int paramB;

private Singleton() {

this.paramA = Config.PARAM_A;

this.paramB = Config.PARAM_B;

}

public synchronized static Singleton getInstance() {

if (instance == null) {

instance = new Singleton();

}

return instance;

}

}

那么,对单例,我们有什么解决方案吗?

我们可以使用静态方法来替代单例,但是,更加的不灵活,做不到延迟加载

除此外,我们还可以通过工厂模式,IOC容器来保证,甚至可以利用程序员自身来控制,只要保证不编写第二个类对象就可以

这就类似于Java内存中释放交给JVM保证,C++中交给程序员保证

本章的重点如下

单例对于OOP特性的支持并不好

单例会隐藏类之间的依赖关系

单例对于代码的扩展性并不好

单例对于可测试性并不好

单例不支持有参数的注入

如何替代单例

我们可以使用静态方法去替代,但是其并不能完美的替代,我们只能尝试使用别的方式,比如工厂模式,IOC容器等,甚至可以由程序员来替代

虽然单例有些不好,但是如果有一些全局的类,很适合使用单例的话,设置为单例也是很方便的

发表评论

邮箱地址不会被公开。 必填项已用*标注