设计模式-单例模式

单例模式

单例模式,也叫单子模式,是一种常用的软件设计模式,属于创建型模式的一种。在应用这个模式时,单例对象的必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。—— 维基百科

保证一个类仅有一个实例,并提供一个访问它的全局访问点。

单例模式是一种常用的模式,有一些对象我们往往只需要一个,比如线程池、全局缓存、浏览器中的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章 单例模式

https://reftoringguru.cn/design-patterns/singleton