4

前言

设计模式是面向对象软件设计过程中针对特定问题的简洁而优雅的解决方案,每一种设计模式都能解决一类特定的问题,这对程序员解决问题的能力提高很有帮助,因此最近开始整理设计模式相关的整理,这是第一篇:单例模式,接下来就进入单例模式

定义

单例模式:单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,单例是一种创建型模式。

实现

实现一个单例模式并不复杂,实现思路就是用一个变量来标志当前是否已经为某个类创建过对象,如果是,则在下一次获取该类的实例时,直接返回之前创建的对象,举个例子:

let Singleton = function (name) {
  this.name = name
  this.instance = null
}
Singleton.prototype.getName = function () {
  return this.name
}
Singleton.getInstance = function (name) {
  if (!this.instance) {
    this.instance = new Singleton(name)
  }
  return this.instance
}
let a = Singleton.getInstance('a')
let b = Singleton.getInstance('b')
console.log(a === b) // true
console.log(a.getName()) // a
console.log(b.getName()) // a

通过getInstance方法获取Singleton类的唯一实例,虽然简单,但是不够优雅直接,按照以往的经验,获取实例都是通过new Singleton()的方式来获取的,接下来就来看看更加优雅的实现:

let Singleton = (function () {
  let instance = null
  let Singleton = function (name) {
    if (instance) {
      return instance
    }
    this.name = name
    return instance = this
  }
  Singleton.prototype.getName = function () {
    return this.name
  }
  return Singleton
})()
let a = new Singleton('a')
let b = new Singleton('b')
console.log(a === b) // true
console.log(a.getName()) // a
console.log(b.getName()) // a

这例子用了一个自执行函数返回了Singleton的构造函数,这种实现解决了上一个例子的优雅实现的问题,但是又带来新的问题,这里getName方法是写在Singleton内部了,Singleton函数做了两件:返回单一实例,执行getName业务方法,这种写法不利于拓展,如果后面要加入更多的业务方法,那么Singleton函数内部就会越来越臃肿,因此需要将获取实例和业务方法分离,来看个例子:

let Singleton = function (name) {
  this.name = name
}
Singleton.prototype.getName = function () {
  return this.name
}

let ProxySingleton = (function () {
  let instance = null
  return function (name) {
    if (!instance) {
      instance = new Singleton(name)
    }
    return instance
  }
})()

let a = new ProxySingleton('a')
let b = new ProxySingleton('b')
console.log(a === b) // true
console.log(a.getName()) // a
console.log(b.getName()) // a

这个例子中为了将获取instance单例和业务方法分离,引入了代理类ProxySingleton,ProxySingleton专门负责生成单例,而业务方法则由Singleton类负责,这样就将二者职责分离了

应用

单例模式应用十分广泛,比如线程池、全局缓存、浮窗等,接下来就介绍两个比较常用的场景

全局缓存

有时我们希望用一个全局变量在一次会话中缓存一些数据,会话结束就自动清除,这时候我们可以考虑利用单例模式创建一个缓存对象,举个例子:

let Storage = function () {
  this.store = {}
}
Storage.prototype.setItem = function (key, value) {
  this.store[key] = value
}
Storage.prototype.getItem = function (key) {
  return this.store[key]
}
let Cache = (function () {
  let instance = null
  return function () {
    if (!instance) {
      instance = new Storage()
    }
    return instance
  }
})()

export {
  Cache
}

上面例子中就定义来一个Cache缓存类专门生成单例,Storage类用于存储,职责分离,这样就可以在一次会话中缓存自己想要的数据

浮窗

在页面中我们为了程序更友好,总是会有各种各样的提示弹窗,在需要的地方显示然后消失,实际上我们创建弹窗可以是同一个,利用单例模式,如果下回生成,发现已经实例过一个弹窗就直接返回,这样可以减少开销,举个例子:

let createLayer = function () {
  let div = document.createElement('div')
  div.innerHTML = '浮窗'
  return div
}
let getSingleton = function (fn) {
  let result = null
  return function () {
    return result || (result = fn.apply(this, arguments))
  }
}
let createLayerSingleton = getSingleton(createLayer)
let layer = createLayerSingleton()
let layer1 = createLayerSingleton()
console.log(layer === layer1) // true
document.body.append(layer)

这个例子也是类似,将创建弹窗的方法和获取实例的方法分离,这职责单一,利于扩展,如果后面需要生成toast的方法,那就新增一个createToast的方法,然后同样调用getSingleton,将createToast传入就可以得到一个toast的实例

优缺点

优点:

  • 提供了对唯一实例的受控访问,因为单例类封装了它的唯一实例,因此可以控制实例的访问形式
  • 由于只有一个对象,因此可以节约网页资源提高网页性能

缺点:

  • 传统的单例模式不利于扩展,同时单例类既提供实例有提供业务方法,违背单一职责原则,但是上面例子有介绍通过一个代理类,将提供实例和提供业务方法的类分离,这样就能职责单一,利于扩展

总结

这篇文章是对最近看的单例设计模式的一点总结,提供一些简单的例子和应用场景,希望大家看了之后能对单例模式有所了解。

如果有错误或不严谨的地方,欢迎批评指正,如果喜欢,欢迎点赞


william
2.7k 声望826 粉丝

love and share