单例是一种常见的设计模式,但是实际开发中,有些人会认为单例是一种反模式
那么为什么单例是一种反模式
不用单例的话,如何解决呢?
单例存在的问题
我们使用单例,大多是为了表示一个全局唯一的类,比如配置类,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容器等,甚至可以由程序员来替代
虽然单例有些不好,但是如果有一些全局的类,很适合使用单例的话,设置为单例也是很方便的