JavaScript设计模式———单例模式
单例模式的定义是: 保证一个类仅有一个实例,并提供一个访问它的全局访问点。
单例模式的核心是确保只有一个实例,并提供全局访问
不透明单例模式
例子:
function SingleInstance(name) {
this.name = name
this.instance = null
}
SingleInstance.getInstance = function(name) {
if (!this.instance) {
this.instance = new SingleInstance(name)
}
return this.instance
}
let instance1 = SingleInstance.getInstance('测试1')
let instance2 = SingleInstance.getInstance('测试2')
console.log(instance1 === instance2) //true
问题:
我们通过 SingleInstance.getInstance 来获取 SingleInstance类的唯一对象,这种方式相对简单,但有一个问题,就是增加了这个类的不透明性, SingleInstance类的使用者必须知道这是一个单例类,跟以往通过 new XXX 的方式来获取对象不同,这里偏要SingleInstance.getInstance 来获取对象。
透明单例模式
自执行的匿名函数和闭包,并且让这个匿名函数返回真正的 SingleInstance 构造方法;闭包保留了之前实例的对象
let SingleInstance = (function() {
let instance
let SingleInstance = function(name) {
this.name = name
return instance || (instance = this)
}
return SingleInstance
})()
let instance1 = new SingleInstance('测试1')
let instance2 = new SingleInstance('测试2')
console.log(instance1 === instance2) //true
问题:
这种创建单例模式中,类既需要实例化对象,又要保证是单一对象;破坏了单一职责原则;
如果单一实例类,需要创建多个不同对象时,还需要修改这个类
把管理单一实例管理过程抽象出来(不变的逻辑),类对象只需要进行自己的实例化操作
代理实现单例模式
用一个封装函数(代理函数)来包裹实际创建类的方法,负责管理单例的逻辑移到了代理类 proxySingletonCreateClass中,
function SingleInstance(name) {
this.name = name
}
let proxySingletonCreateClass = (function() {
let instance
return function(...args) {
if (!instance) {
instance = new SingleInstance(...args)
}
return instance
}
})()
let instance1 = new proxySingletonCreateClass('测试1')
let instance2 = new proxySingletonCreateClass('测试2')
console.log(instance1 === instance2) // true
关于JS中的单例模式的说明
前面提到的几种单例模式的实现,更多的是接近传统面向对象语言中的实现,单例对象从“类”中创建而来。在以类为中心的语言中,这是很自然的做法。比如在 Java 中,如果需要某个对象,就必须先定义一个类,对象总是从类中创建而来的。
但 JavaScript 其实是一门无类( class-free)语言,也正因为如此,生搬单例模式的概念并无意义。在 JavaScript 中创建对象的方法非常简单,既然我们只需要一个“唯一”的对象,为什么要为它先创建一个“类”呢?这无异于穿棉衣洗澡,传统的单例模式实现在 JavaScript 中并不适用。
单例模式的核心是确保只有一个实例,并提供全局访问
全局变量不是单例模式,但在 JavaScript 开发中,我们经常会把全局变量当成单例来使用。例如:
var a = {};
当用这种方式创建对象 a 时,对象 a 确实是独一无二的。 如果 a 变量被声明在全局作用域下,则我们可以在代码中的任何位置使用这个变量,全局变量提供给全局访问是理所当然的。这样就满足了单例模式的两个条件。
但是全局变量存在很多问题,它很容易造成命名空间污染, JavaScript 中的变量也很容易被不小心覆盖
作为普通的开发者,我们有必要尽量减少全局变量的使用,即使需要,也要把它的污染降到最低。以下几种方式可以相对降低全局变量带来的命名污染。
使用命名空间
最简单的方法依然是用对象字面量的方式:
var namespace1 = {
a: function() {
alert(1);
},
b: function() {
alert(2);
}
};
使用闭包封装私有变量
这种方法把一些变量封装在闭包的内部,只暴露一些接口跟外界通信:
var user = (function() {
var __name = 'sven',
__age = 29;
return {
getUserInfo: function() {
return __name + '-' + __age;
}
}
})();
惰性单例
在透明单例和代理实现单例模式中,都需要使用一个自执行函数来实现,不使用时创建一个空单例对象;造成不必要的对象;
而且需要不同类的单例多想,需要多次重写上面的代码;
惰性单例指的是在需要的时候才创建对象实例。
抽离管理单一实例过程(不变逻辑),然后将不同创建对象类(可变逻辑)作为参数传入该过程
function SingleInstance(name) {
this.name = name
}
let getSingle = function(fun) {
let result
return function(...args) {
if (!result) {
result = new fun(...args)
}
return result
}
}
let instances = getSingle(SingleInstance)
let instance1 = instances('测试1')
let instance2 = instances('测试2')
console.log(instance1 === instance2) //true
把创建实例对象的职责和管理单例的职责分别放置在两个方法里,这两个方法可以独立变化而互不影响,当它们连接在一起的时候,就完成了创建唯一实例对象的功能。