单例模式的概念
单例模式是一种非常常用但又非常简单的设计模式。它是指在一个类中,只能有一个实例对象,即使多次实例化该类,也只能返回第一次实例化该类后的实例对象。单例模式的作用就是可以减少许多不必要的内存开销,节省内存空间,提高性能。
许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。
单例模式思路
对于代码开发中,一个类同时只有一个实例对象的情况就叫做单例。那么,如何保证一个类只能有一个对象呢?
我们知道,在面向对象的思想中,通过类的构造函数可以创建对象,只要内存足够,可以创建任意个对象。
所以,要想限制某一个类只有一个单例对象,就需要在它的构造函数上下功夫。
所以实现对象单例模式的思路
就是:
1、一个类能返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法,通常使用getInstance这个名称);
2、当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用;
3、同时我们还将该类的构造函数定义为私有方法,这样其他处的代码就无法通过调用该类的构造函数来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例。
没有使用单例模式的情况
实例化一个类A 开辟一个内存空间 p1
再次实例化这个类A 又开辟一个内存空间 p2
实例化多少次,就开辟多少个内存空间
使用单例模式的情况
实例化一个类A 开辟一个内存空间 p1
再次实例化这个类A 获得一个内存空间,不过获得的是第一次实例化该类开辟的内存空间p1
同一个类无论实例化多少次,都只返回第一次实例化的对象
单例模式的优点
- 在单例模式中,活动的单例只有一个实例,对单例类的所有实例化得到的都是相同的一个实例。这样就 防止其它对象对自己的实例化,确保所有的对象都访问一个实例
- 单例模式具有一定的伸缩性,类自己来控制实例化进程,类就在改变实例化进程上有相应的伸缩性。
- 提供了对唯一实例的受控访问。
- 由于在系统内存中只存在一个对象,因此可以节约系统资源,当需要频繁创建和销毁的对象时单例模式无疑可以提高系统的性能。
- 允许可变数目的实例。
- 避免对共享资源的多重占用。
单例模式的缺点
- 不适用于变化的对象,如果同一类型的对象总是要在不同的用例场景发生变化,单例就会引起数据的错误,不能保存彼此的状态。
- 由于单利模式中没有抽象层,因此单例类的扩展有很大的困难。
- 单例类的职责过重,在一定程度上违背了“单一职责原则”。
- 滥用单例将带来一些负面问题,如为了节省资源将数据库连接池对象设计为的单例类,可能会导致共享连接池对象的程序过多而出现连接池溢出;如果实例化的对象长时间不被利用,系统会认为是垃圾而被回收,这将导致对象状态的丢失。
单例模式适用场景
单例模式只允许创建一个对象,因此节省内存,加快对象访问速度,因此对象需要被公用的场合适合使用,如多个模块使用同一个数据源连接对象等等。如:
1、需要频繁实例化然后销毁的对象。
2、创建对象时耗时过多或者耗资源过多,但又经常用到的对象。
3、有状态的工具类对象。频繁访问数据库或文件的对象。
以下都是单例模式的经典使用场景:
资源共享的情况下
,避免由于资源操作时导致的性能或损耗。如数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,使用单例模式来维护,就可以大大降低这种损耗。
控制资源的情况下
,方便资源之间的互相通信。如线程池,多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。
以下是模拟ES5中没有使用单例模式
的情况:
function Demo(){
this.name = "remi"
this.age = 18
this.sex = "精壮的成年男子"
}
var remi = new Demo() // 此时实例化该构造函数,获得一个对象
console.log(remi.name)
var tongtong = new Demo() // 此时又实例化该构造函数,获得另一个对象
console.log(tongtong.name)
console.log(remi === tongtong) // 这时肯定是false,是两个完全不同的对象
复制代码
以下是模拟ES5中使用单例模式
的情况:
// 静态属性和静态方法不需要new,直接打点调用即可
function Demo(name){
// 如果是第一次实例化的话 Demo.unique === undefined;如果不是第一次实例化,那么Demo.unique是有值的等于实例化后的对象this,就不是undefined
if(Demo.unique !== undefined){ // 如果不全等与undefined,说明不是第一次实例化,既然不是第一次实例化,那就进行该操作
return Demo.unique // 那就返回第一次实例化后的对象
}
// 如果是第一次实例化,Demo.unique就是undefined,就不走上面的if,就进行下面操作
this.name = name
this.age = 18
this.sex = "boy"
// 添加一个独一无二的静态的属性,将unique属性赋值为第一次实例化之后的对象this,以后每次实例化都是用该对象
Demo.unique = this // this代表实例化后的对象
}
var remi = new Demo("remi") // 此时实例化该构造函数,获得一个对象
console.log(remi.name)
var tongtong = new Demo("tongtong") // 此时又实例化该构造函数,获得同一个对象
console.log(tongtong.name)
console.log(remi === tongtong) // 这时肯定是true,这两个都是同一个实例对象
复制代码
以下是模拟ES6中使用单例模式
的情况:
class Demo{
constructor(){
this.name = "remi"
}
// 静态方法,获取单例模式
static getInstance(){
if(!this.instance){ // this.instance是自己创造的一个属性,一开始肯定是undefined,取反则为true,进入该if判断并执行其中代码
this.instance = new Demo() // 将this.instance赋值为实例化该类的对象
}
return this.instance // 返回this.instance,第一次为undefined,进入if判断,实例化该类Demo,得到第一次实例化的对象
// 之后每次实例化该类Demo,this.instance都已经有值了,是第一次实例化后的对象,则不进入if判断,直接返回第一次实例化后的对象
}
}
let Remi = Demo.getInstance() // 第一次实例化并调用单例模式静态方法
let TongTong = Demo.getInstance() // 第二次实例化并调用单例模式静态方法
console.log(Remi === TongTong) // true
let Remi1 = new Demo() // 如果不调用单例模式方法,则生成的不是单例模式对象
let TongTong1 = new Demo()
console.log(Remi1 === TongTong1) // false
复制代码