『面试的底气』—— 设计模式之单例模式

·  阅读 1338

定义

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

自己的理解

通常我们可以让一个全局变量使得一个对象被访问,但它不能防止实例化多个对象。一个最好的办法就是,让类自身负责保持它的唯一实例,且这个类保证没有其他实例可以被创建,并且它可以提供一个访问该实例的方法。

实现单例模式

要实现一个简单的单例模式并不复杂,无非是用一个变量来缓存一个类实例化生成的对象,然后用这个变量来判断一个类是否已经实例化过。如何变量有值,则在下一次要获取该类的实例化生成的对象时,直接返回这个变量(之前实例化生成的对象)。

var Singleton = function(name) {
    this.name = name;
    this.instance = null;
};
Singleton.prototype.init = function() {
   //...初始化
};
Singleton.getInstance = function(name) {
    if (!this.instance) {
        this.instance = new Singleton(name);
        this.instance.init();
    }
    return this.instance;
};
var a = Singleton.getInstance('a');
var b = Singleton.getInstance('b');
console.log(a === b); // true

正常来说一个类每次实例化生成的对象是不同的,在上述代码中分别使用Singleton.getInstance获取Singleton类实例化生成的对象并赋值给对象a和对象b,执行console.log(a === b)打印出true,说明上述代码实现单例模式成功。

或不把实例化生成的对象挂载到类的属性上也可以实现,代码如下所示:

var Sington = function( name ){
   this.name = name;
};
Singleton.prototype.init = function() {
   //...初始化
};
Singleton.getInstance = (function(){
 var instance = null;
 return function( name ){
     if ( !instance ){
       instance = new Singleton( name );
       instance.init();
     }
     return instance;
 }
})(); 

变量instance用来缓存类实例化生成的对象,用自执行匿名函数创建了一个闭包,把变量instance存储在闭包中,每次执行Singleton.getInstance时就可以用变量instance来判断Singleton类是否已经实例化过。

透明的单例模式

上述实现单例模式的方法中有一个问题,就是增加了这个类的“不透明性”,Singleton类的使用者必须知道这是一个单例类,跟以往通过new XXX的方式来实例化类,这里要使用Singleton.getInstance来实例类。故要换一种方法来实现透明的单例模式。

const Singleton = (function() {
    let instance;
    const Singleton = function(name) {
        if (instance) {
            return instance;
        }
        this.name = name;
        this.init();
        return instance = this;
    };
    Singleton.prototype.init = function() {
        //...初始化
    };
    return Singleton;
})();
const a = new Singleton('a');
const b = new Singleton('b');
console.log(a === b); // true

以上代码中,Singleton类的构造函数是一个自执行的匿名函数,会构成一个闭包,可以把判断类是否实例化过的和缓存类实例化生成的对象的变量instance缓存起来,最后在返回真正的Singleton类构造方法。那么就可以使用new Singleton来实例化Singleton类。

必须遵循设计模式的原则

上述代码还不够完美,有两点:

  • Singleton类的真正的构造函数放在自执行的匿名函数中返回,有点奇怪。

  • Singleton类的构造函数中违背了设计模式的单一职责原则,做了二件事情,创建类实例化生成的对象和执行初始化init方法,保证了只能实例化一次。

实现任何设计模式都要遵循设计模式的原则,要把“创建类实例化生成的对象和执行初始化init方法”和“保证了只能实例化一次”这两个职责分隔开来。下面用代理实现单例模式。

const Singleton = function (name) {
  this.name = name;
  this.init();
}
Singleton.prototype.init = function () {
  //...初始化
};
const PropxSingleton = (function () {
  var instance;
  return function (name) {
    if (!instance) {
      instance = new Singleton(name);
    }
    return instance;
  }
})();
var a = new PropxSingleton('a');
var b = new PropxSingleton('b');
console.log(a === b);//true

以上用代理类propxSingleton实现了Singleton类的单例模式,而Singleton类仍旧是一个普通的类,不影响在其它地方的使用。

然后以上代码还不够完美,假如要给另外一个类实现单例模式,得去修改代理类propxSingleton的代码,不符合设计模式中的开放-封闭原则,得封装一个获取不同类的代理类propxSingletongetPropxSingleton方法。

const Singleton = function (name) {
  this.name = name;
  this.init();
}
Singleton.prototype.init = function () {
  //...初始化
};
const getPropxSingleton = function (customClass) {
  return (function () {
    var instance;
    return function (name) {
      if (!instance) {
        instance = new customClass(name);
      }
      return instance;
    }
  })()
};
const PropxSingleton = getPropxSingleton(Singleton);
var a = new PropxSingleton('a');
var b = new PropxSingleton('b');
console.log(a === b); //true

用ES6来实现一个单例模式

以上都是用ES5实现的单例模式,现在都是使用ES6了,故用ES6来实现一个单例模式。

class Singleton {
  constructor(name) {
    this.name = name;
    this.init();
  }
  init() {
    //...初始化
  }
}
const getPropxSingleton = function (customClass) {
  let instance = null;
  return class {
    constructor() {
      if (instance) return instance;
      return instance = new customClass(...arguments)
    }
  }
};
const PropxSingleton = getPropxSingleton(Singleton);
var a = new PropxSingleton('a');
var b = new PropxSingleton('b');
console.log(a === b); //true
分类:
前端
分类:
前端
收藏成功!
已添加到「」, 点击更改