单例模式
一、基本思想
确保只有一个实例,并提供全局访问。
用一个变量来标志当前是否已经为某个类创建过对象
- 如果已经创建过,则直接返回之前创建的对象
- 如果没有创建过,则创建并返回
二、明确定义一个单例
用户必须以单例设计者指定的方法来创造新实例/获取已有实例,缺点在于:用户需要知道这个是指定的方法,即不能用 new 操作符像其他对象一样创造实例
基本思想的实现方式1:用一个 instance 变量来控制,每次新建的时候做一次判断
// 构造函数
var Singleton = function (name) {
this.name = name
this.instance = null
}
// 创建一个实例
Singleton.prototype.createOrGet = function (name) {
// 如果没有创建过,则创建并返回
if (!this.instance) {
return this.instance = new Singleton(name)
}
// 如果已经创建过,则直接返回之前创建的对象
return instance
}
基本思想的实现方式2:利用IIFE和闭包来保存已创建实例
- 第一次创建之前,instance 变量设置为 null 代表还没有实例
- 由于闭包的性质,返回的函数一直存在,故这个外层函数的作用域也一直存在,instance当然也一直存在
- 在下文instance被实例化之后并一直存在,代表已经有了实例
var Singleton = function(name) {
this.name = name
}
// createOrGet 创建立即执行,返回内部的函数 function (name) {...}
Singleton.createOrGet = (function(){
var instance = null
return function (name) {
if (!instance) {
return instance = new Singleton(name)
}
return instance
}
})()
三、用代理包装一个实例
假设 SingletonObject 类是我们需要的一个单例模式的类
我们希望可以通过 new 操作符来创建或获得现有实例
var SingletonObject = function(name) {
this.name = name
}
用 Proxy 类来包装创建SingletonObject实例的过程
var Proxy = (function(){
var instance = null
return function (name) {
if (!instance) {
return instance = new SingletonObject (name)
}
return instance
}
})()
现在可以像其他对象一样,用 new 操作符来创建实例
测试一下:
var a = new Proxy('a') // SingletonObject {name: "a"}
var b = new Proxy('b') // SingletonObject {name: "a"}
a === b // true
四、单一职责原则:抽离通用逻辑
创建单例的通用逻辑:
- 如果是初次创建,即创建实例并返回
- 如果是再次创建,则直接返回现有实例
if (!instance) {
// create new instance
}
return instance
那么我们可以将这段代码的分离出来
提供上文 create new instance 部分的方法 fn
// 抽离出创造单例的通用逻辑
var getSingle = function (fn) {
var instance = null
return function () {
if (!instance) {
instance = fn.apply(this, arguments) // arguments是闭包的参数
}
return instance
}
}
有了 getSingle,我们就可以正常的编写对象的构造函数
// 各种各样的创造实例的方法
// LoginWindow constructor
var LoginWindow = function (name) {
var loginWindow = document.createElement('div')
loginWindow.innerHTML = `我是登录窗口${name}`
document.body.appendChild(loginWindow)
return loginWindow
}
// Teacher constructor
function Teacher(name) {
this.name = name
}
// Car constructor
var Car = function (name) {
var car = new Object()
car.name = name
return car
}
有了一些正常的构造函数,我们就可以将其作为 不变(单例的逻辑)中的变数(不同的新建实例方法),给到 getSingle 通用逻辑中:
var createSingleLoginWindow = getSingle(LoginWindow)
var createSingleTeacher = getSingle(Teacher)
var createSingleCar = getSingle(Car)
测试一下是否通用,是否能跑通:
var login1 = createSingleLoginWindow('1') // <div>我是登录窗口1</div>
var login2 = createSingleLoginWindow('2') // <div>我是登录窗口1</div>
login1 === login2 // true
// 可能不能用function XXX(name) {} 形式的创建实例的方法
var teacher1 = createSingleTeacher('alice') // undefined
var teacher2 = createSingleTeacher('bob') // undefiend
var car1 = createSingleCar('benz') // {name: "benz"}
var car2 = createSingleCar('bmw') // {name: "benz"}
car1 === car2 // true