JavaScript设计模式:单例模式

前言

总是想绕却始终绕不开的设计模式,决定花点时间弄明白,先从单例模式开始。在开发工程师眼里,单例就是保证一个类只有一个实例,实现的方法一般是先判断实例存在与否,如果存在直接返回,如果不存在就创建了再返回,这就确保了一个类只有一个实例对象。在JavaScript里,单例作为一个命名空间提供者,从全局命名空间里提供一个唯一的访问点来访问该对象。

定义

单例模式之所以这么叫,是因为它限制一个类只能有一个实例化对象。经典的实现方式是,创建一个类,这个类包含一个方法,这个方法在没有对象存在的情况下,将会创建一个新的实例对象。如果对象存在,这个方法只是返回这个对象的引用。如果创建多个对象,一定记住,除了第一个对象之外,其他的对象均是此对象的引用,这样来理解单例模式就容易的多。

实现

在JavaScript语言中, 单例服务作为一个从全局空间的代码实现中隔离出来共享的资源空间是为了提供一个单独的函数访问指针。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
const mySingleton = (function (){
let instance = null;
function init(){
// 私有的成员函数和私有变量
function privateMethod(){
console.log("I am a private Method.");
}
const private_var = "I am a private data.";
const private_num = Math.random();
return {
// return出去的函数是属于公有的方法
publicMethod: function (){
console.log("Public can see me.");
privateMethod();
},
getRandomNumber:function(){
return private_num;
},
public_var: "I am a public data.",
}
}

return {
getInstance: function(){
if(!instance){
instance = init();
}
return instance;
}
}
})();
// 调用
const A_obj = mySingleton.getInstance();
const B_obj = mySingleton.getInstance();
console.log(A_obj.getRandomNumber() === B_obj.getRandomNumber()); //控制台打印true,说明B_obj只是对A_obj的一个引用

console.log(A_obj.public_var); //访问return对象中的公有对象,可访问;若直接访问initial()中的私有变量,则报错。
A_obj.publicMethod(); //访问公有的成员函数,成员函数中的私有变量或者私有函数只可以通过共由的方法来访问,保证了封装性。

注释

在上述代码的调用阶段,A_obj 和B_obj 均是通过mySingleton调用了getInstance()方法获取的,在该方法中我只知道是通过判断是否存在一个实例对象再去确定是否创建新的对象,有人可能会误以为这是导致其余对象是实例对象的引用,其实不然,若将函数的代码段修改为始终初始化对象,会发现结果返回的仍然是true,这也证实了单例模式只有一个实例对象的原理。

拓展

在四人帮(GoF)的书里面,单例模式的应用描述如下:

  • 每个类只有一个实例,这个实例必须通过一个广为人知的接口,来被客户访问。
  • 子类如果要扩展这个唯一的实例,客户可以不用修改代码就能使用这个扩展后的实例。

关于第二点,可以参考如下的实例,我们需要这样编码:

1
2
3
4
5
6
7
8
9
10
mySingleton.getInstance = function(){
if(!this.instance){
if(Math.random()>0.5){
this.instance = this.init();
}else{
this.instance = this.init_2();
}
return this.instance;
}
};

总结