设计模式-单例模式
单例模式
单例模式,也叫单子模式,是一种常用的软件设计模式,属于创建型模式的一种。在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。—— 维基百科
保证一个类仅有一个实例,并提供一个访问它的全局访问点。
单例模式是一种常用的模式,有一些对象我们往往只需要一个,比如线程池、全局缓存、浏览器中的window对象等。
实现
所有单例的实现都包含以下两个相同的步骤:
- 将默认构造函数设为私有, 防止其他对象使用单例类的
new
运算符。 - 新建一个静态构建方法作为构造函数。 该函数会 “偷偷” 调用私有构造函数来创建对象, 并将其保存在一个静态成员变量中。 此后所有对于该函数的调用都将返回这一缓存对象。
标准的单例模式
用一个变量来标志当前是否已经为某个类创建过对象,如果是,则在下一次获取该类的实例时,直接返回之前创建的对象。
// 实现1
var Singleton = function(name) {
this.name = name
this.instance = null
}
Singleton.prototype.getName = function() {
console.log(this.name)
}
Singleton.getInstance = function(name) {
if (!this.instance) {
this.instance = new Singleton(name)
}
return this.instance
}
var a = Singleton.getInstance('test1')
var b = Singleton.getInstance('test2')
console.log('Singleton1', a === b)
// 实现2
var Singleton = function(name) {
this.name = name
}
Singleton.prototype.getName = function() {
console.log(this.name)
}
// 惰性单例: 指在需要的时候才创建对象实例.在调用的时候才创建实例,而不是页面加载好的时候就创建
Singleton.getInstance = (function() {
var instance = null
return function(name) {
if (!instance) {
instance = new Singleton(name)
}
return instance
}
})()
var a = Singleton.getInstance('test1')
var b = Singleton.getInstance('test2')
console.log('Singleton2', a === b)
使用 class 的方式进行改写
class Singleton {
constructor(name) {
this.name = name
}
static getInstance(name) {
if (!Singleton.instance) {
Singleton.instance = new Singleton(name)
}
return Singleton.instance
}
}
const test = Singleton2.getInstance('str1')
const test2 = Singleton2.getInstance('str2')
console.log(test.name, test2.name, test === test2)
// str1 str1 true
优化
透明的单例模式:用户从这个类中创建对象的时候,可以像使用其他任何普通类一样,但是使用了自执行的匿名函数和闭包,增加了程序的复杂度和可读性降低。
使用CreateDiv单例类,它的作用是负责在页面中创建唯一的div节点。
var CreateDiv = (function () {
var instance
// 构造函数
var CreateDiv = function (html) {
if (instance) {
return instance
}
this.html = html
this.init()
return instance = this
}
CreateDiv.prototype.init = function () {
var div = document.createElement('div')
div.innerHTML = this.html
document.body.appendChild(div)
}
return
})()
var a = new CreateDiv('test3')
var b = new CreateDiv('test4')
console.log(a === b) // true
用代理实现单例模式
var CreateDiv = function(html){
this.html = html
this.init()
}
CreateDiv.prototype.init = function(){
var div = documet.createElement('div')
div.innerHTML = this.html
document.body.appendChild(div)
}
// 使用代理的方式处理单例逻辑
var ProxySingletonCreateDiv = (function(){
var instance
return function(html){
if(!instance){
instance = new CreateDiv(html)
}
return instance
}
})()
var a = new ProxySingletonCreateDiv('test5')
var b = new ProxySingletonCreateDiv('test6')
console.log(a === b) // true
注意点
创建对象和管理单例的职责被分布在两个不同的方法中,这两个方法组合起来才具有单例模式的威力。
避免设置全局变量。全局变量容易脏成空间污染,容易发生变量名冲突,被其他代码覆盖。
使用命名空间
var MyApp = {} MyApp.namespace = function(name) { var parts = name.split('.') var current = MyApp for (var i in parts) { if (!current[parts[i]]) { current[parts[i]] = {} } current = current[parts[i]] } } MyApp.namespace('event') MyApp.namespace('dom.style') // 等价于 var MyApp = { event: {}, dom: { style: {} } }
使用闭包封装私有变量
var user = (function() { var __name = 'test', __age = 29 return { getUserInfo: function() { return __name + '-' + __age } } })()
python 中会约定两个下划线表示私有属性。
动态编程语言更需要依赖开发者的意识。
优缺点
优点
- 可以保证一个类只有一个实例。
- 获得了一个指向该实例的全局访问节点。
- 仅在首次请求单例对象时对其进行初始化。
缺点
- 违反了_单一职责原则_。 该模式同时解决了两个问题。
- 单例模式可能掩盖不良设计, 比如程序各组件之间相互了解过多等。
- 该模式在多线程环境下需要进行特殊处理, 避免多个线程多次创建单例对象。
- 中大型项目的应用中,全局变量的维护该是考虑的成本。
应用与实践
在工具库中常见。
比如 vuex 会创建一个全局的 store 仓库,维护全局的 store 数据,所有的相关操作都会依赖这个全局的 store 实例。
在小程序中使用。根据业务需求,比如只需要一个 WebSocket 连接,则就可以创建一个全局 WebSocket 对象,方便进行管理。
使用单例模式可以实现其他复杂的设计模式。
参考文章
《JavaScript设计模式与开发实践》 - 第4章 单例模式