posts - 297,  comments - 1618,  trackbacks - 0

1、   什么是单例模式?

      单例模式的意思就是只有一个实例。单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。这个类称为单例类。

      单例模式的要点如下:

1)一个类只能有一个实例;

2)自己创建这个实例;

3)整个系统都要使用这个实例。

单例模式的示意图如下所示:
   

在上面的对象图中,有一个“单例对象”,而“客户甲”、“客户乙”和“客户丙”是单例对象的三个客户对象。可以看到,所有的客户对象共享一个单例对象,而且从单例对象到自身的连接线可以看出,单例对象持有对自己的引用。

2、   单例模式的应用场景

单例模式作为一种比较常见的设计模式,作用如下:

1)控制资源的使用,通过线程同步来控制资源的并发访问;

2)控制实例产生的数量,达到节约资源的目的;

3)作为通信媒介使用,也就是数据共享,它可以在不建立直接关联的条件下,让多个不相关的两个线程或者进程之间实现通信。

单例模式常见的参考使用如下:

1)资源管理:在计算机系统中,需要管理的资源包括软件外部资源,譬如每台计算机可以有若干个打印机,但只能有一个Printer Spooler 以避免两个打印作业同时输出到打印机中。每台计算机可以有若干传真卡,但是只应该有一个软件负责管理传真卡,以避免出现两份传真作业同时传到传真卡中的情 况。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。这些资源管理器构件必须只有一个实例

2)回收站等应用:在整个视窗系统中,Windows的回收站只能有一个实例,整个系统都使用这个惟一的实例,而且回收站自行提供自己的实例。因此,回收站是单例模式的应用。

3、   单例模式的两种形式

根据在建立单例对象的时间不同,可以形象的将单例模式分为两种实现形式:“饿汉式”和“懒汉式”。

3.1 “饿汉式”单例模式实现

“饿汉式”是在不管你用的用不上,一开始就建立这个单例对象,例如如下Java代码实现了饿汉式的单例模式:
package singleton;

/**
 * 饿汉式单例模式
 * @author AmigoXie
 */
public class Singleton {
    private Singleton() {
        System.out.println("构造器");
    }

    //在自己内部定义自己一个实例,注意这是private 只供内部调用
    private static Singleton instance = new Singleton();
    //这里提供了一个供外部访问本class的静态方法,可以直接访问
    public static Singleton getInstance() {
        return instance;
    }

    public static void main(String[] args) {
        System.out.println("测试饿汉式单例模式");
        Singleton.getInstance();
    }
}
该例的运行结果如下所示:
构造器
测试饿汉式单例模式










由此可知在调用单例对象的静态方法getInstance()之前,构造器Singleton()已被调用。

3.2 “懒汉式”单例模式实现

“懒汉式”是在你真正用到的时候才去建这个单例对象。,例如如下Java代码实现了懒汉式的单例模式:
package singleton;

/**
 * 懒汉式单例模式
 * 
@author AmigoXie
 
*/

public class Singleton2 {
    
private Singleton2() {
        System.out.println(
"构造器");
    }


    
private static Singleton2 instance = null;

    
public static synchronized Singleton2 getInstance() {
        
//这个方法比上面有所改进,不用每次都进行生成对象,只是第一次使用时生成实例
        if (instance == null{
        instance 
= new Singleton2();
        }

        
return instance;
    }


    
public static void main(String[] args) {
        System.out.println(
"测试懒汉式单例模式");
        Singleton2.getInstance();
    }

}



     该例的运行结果如下所示:

测试懒汉式单例模式
构造器

     从运行的打印结果表名,在getInstance()获取实例方法被调用时,构造器才被调用。

4、   JavaScript中使用单例模式

JavaScript中,单例(Singleton)模式是最基本又最有用的模式之一。这种模式提供了一种将代码组织为一个逻辑单元的手段,这个逻辑单元中的代码可以通过单一的变量进行访问。确保单例对象只有一份实例,你就可以确信自己的所有代码使用的都是同样的全局资源。

单例类在JavaScript中用途广泛:

1)可以用来划分命名空间,以减少网页中全局变量的数量;

2)可以在一种名为分支的技术中用来封装浏览器之间的差异;

3)可以借助于单例模式,将代码组织得更为一致,从而使代码更容易阅读和维护。

4.1 单例的基本结构

最基本的单例实际上是一个对象字面值,它将一批有一定关联的方法和属性组织在一起。例如如下JavaScript代码:

var Singleton = {
    attribute1: 
true;
    attribute2: 
10
    
    method1: 
function() {
    }
,
    method2: 
function() {
    }
   
}
;

这些成员可以通过Singleton加圆点运算符来访问:

Singleton.attribute1 = false;
var total = Singleton. attribute2 + 5;
var result = Singleton.method1();

对象字面值只是用以创建单例的方法之一,后面介绍的那些方法所创建的单体看起来更像其他面向对象语言中的单例类。另外,并非所有对象字面值都是单体,如果它只是用来模仿关联数组或容纳数据的话,那显然不是单例。但如果它是用来组织一批相关方法和属性的话,那就可能是单例,其区别主要在于设计者的意图。

4.2 创建拥有私有成员的单例

4.2.1 使用下划线表示法

在单例对象内创建类的私有成员的最简单、最直截了当的方法是使用下划线表示法(在JavaScript业界,如果变量和方法是使用下划线,则表示该变量和方法是私有方法,只允许内部调用,第三方不应该去调用)。参考实例如下:

GiantCorp.DataParser = {
    
// 私有方法
    _stripWhitespace: function(str) {
        
return str.replace(/\s+/, ''); 
    }
,
    _stringSplit: 
function(str, delimiter) {
        
return str.split(delimiter);
    }
,

    
// 公用方法
    stringToArray: function(str, delimiter, stripWS) {
        
if (stripWS) {
            str 
= this._stripWhitespace(str);
        }

        
        
var outputArray = this._stringSplit(str, delimiter);
        
return outputArray;
    }

}
;

在如上代码中,stringToArray方法中使用this访问单体中的其他方法,这是访问单体中其他成员或方法的最简便的方法。但这样做有一点风险,因为this并不一定指向GiantCorp.DataParser例如,如果把某个方法用作事件监听器,那么其中的this指向的是window对象,因此大多数JavaScript库都会为事件关联进行作用域校正,例如在这里使用GiantCorp.DataParser比使用this更为安全。

4.2.2使用闭包

在单例对象中创建私有成员的第二种方法是借助闭包。因为单例只会被实例化一次,所以不必担心自己在构造函数中声明了多少成员。每个方法和属性都只会被创建一次,所以可以把它们声明在构造函数内部(因此也就位于同一个闭包中)。

使用闭包创建拥有私有成员的单例类的实例如下:

MyNamespace.Ssingleton = (function() {
    
// 私有成员
     var privateAttribute1 = false;
    
var privateAttribute2 = [123];
 
    
function privateMethod1() {
        
    }
    

    
function privateMethod2() {
        
    }

 
    
return {
        
// 公有成员
        publicAttribute1: true;
        publicAttribute2: 
10,
        
        publicMethod1: 
function() {
            
        }
,
        publicMethod2: 
function() {
            
        }

    }
;
}
)();

这种单例模式又称为模块模式,指的是它可以把一批相关方法和属性组织为模块并起到划分命名空间的作用。

使用该种方式改造4.2.1中的实例,参考代码如下:

GiantCorp.DataParser = (function() {
    
var whiteSpaceRegex = /\s+/;
    
// 私有方法
    function stripWhitespace(str) {
        
return str.replace(whiteSpaceRegex, ''); 
    }


    
function stringSplit(str, delimiter) {
        
return str.split(delimiter);
    }
,

    
return {
        
// 公用方法
        stringToArray: function(str, delimiter, stripWS) {
            
if (stripWS) {
                str 
= stripWhitespace(str);
            }

            
            
var outputArray = stringSplit(str, delimiter);
            
return outputArray;
        }

    }
;
}
)();

将私有成员放在闭包中可以确保其不会在单例对象之外被使用,因此开发人员可以自由的改变对象的实现细节,而不会殃及别人的代码。还可以使用这种办法对数据进行保护和封装。

4.3 JavaScript中实现“懒汉式”单例模式

在如上的代码中,单例对象都是在脚本加载时被创建出来。对于资源密集型或配置开销甚大的单例,更合理的是使用3.2小节中提到的“懒汉式”单例实现。这种实现方式的特别之处在于,对它们的访问必须借助于一个静态方法,例如调用单例类的getInstance()方法获得对象实例。

参考实现代码如下:

MyNamespace.Singleton = (function() {
    
// 定义一个私有的属性,该属性用于储存单例对象
    var uniqueInstance;
    
function constructor() {
        
// 将单态操作放在这里
        
    }


    
return {
        getInstance: 
function() {
            
if (!uniqueInstance) {
                uniqueInstance 
= constructor();
            }

           
            
return uniqueInstance;
        }

    }

}
)();

将一个单例转换为懒汉式加载方式后,必须对调用它的代码进行修改,例如之前调用:

MyNamespace.Singleton.publicMethod1();

应该改成如下代码:

MyNamespace.Singleton.getInstance().publicMethod1();

如果觉得命名空间名称太长,可以创建一个别名来简化它。

4.4 使用单例模式实现分支

分支是一种用来把浏览器之间的差异封装到运行期间进行设置的动态方法中的技术。例如,假设我们需要一个创建XHR对象的方法,这种XHR对象在大多数浏览器中是XMLHttpRequest对象的实例,但在IE早期版本中是某种ActiveX类的实例,这样一种方法通常会进行某种浏览器嗅探或对象探测。如果不用分支技术,那么每次调用这个方法时,所有这些浏览器嗅探代码都要再次运行。如果该方法调用频繁,将会严重影响效率。

要实现获取不同浏览器的XHR对象的功能,参考实现代码的实现步骤如下:

1)判断有多少个分支(有3个),这些分支按其返回的XHR对象类型命名,这三个分支都有一个名为createXhrObject()的方法,该方法返回一个可以执行异步请求的新对象;

2)根据条件将3个分支中某一分支的对象赋给那个变量,具体做法是逐一尝试XHR对象,直到遇到一个当前JavaScript环境所支持的对象为止。

参考代码如下所示:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
    
<head>
        
<title>使用单例模式实现JavaScript中的分支</title>
        
<script type="text/javascript">
            
var SimpleXhrFactory = (function() {
                
// 三个分支
                var standard = {
                    createXhrObject: 
function() {
                        alert(
"standard createXhrObject");
                        
return new XMLHttpRequest();
                    }

                }
;
                
var activeXNew = {
                    createXhrObject: 
function() {
                        alert(
"activeXNew createXhrObject");
                        
return new ActiveXObject("Msxml2.XMLHTTP");
                    }

                }
;
                
var activeXOld = {
                    createXhrObject: 
function() {
                        alert(
"activeXOld createXhrObject");
                        
return new ActiveXObject("Microsoft.XMLHTTP");
                    }

                }
;

                
// 为分支赋值
                var testObject;
                
try {
                    testObject 
= standard.createXhrObject();
                    
return standard;
                }
 catch(e) {
                    
try {
                        testObject 
= activeXNew.createXhrObject();
                        
return activeXNew;
                    }
 catch(e) {
                        
try {
                            testObject 
= activeXOld.createXhrObject();
                            
return activeXOld;
                        }
 catch(e) {
                            
throw new Error("在该浏览器环境中没有发现XHR对象.");
                        }

                    }

                }
 
            }
)();

            SimpleXhrFactory.createXhrObject();

        
</script>

    
</head>

    
<body>

    
</body>

</html>

用了分支技术后,所有的那些特性嗅探代码都只会执行一次,而不是没生成一个对象执行一次。这种技术适用于任何只有在运行时才能确定具体实现的情况。 

5、   单例模式的优缺点

      JavaScript中使用单例模式的主要优点如下:

1)对代码的组织作用:它将相关方法和属性组织在一个不会被多次实例话的单例中,可以使代码的调试和维护变得更轻松。描述性的命名空间还可以增强代码的自我说明性。将方法包裹在单例中,可以防止它们被其它程序员误改。

2)单例模式的一些高级变体可以在开发周期的后期用于对脚本进行优化。

主要缺点如下:

1)因为提供的是一种单点访问,所以它有可能导致模块间的强耦合。单体最好是留给定义命名空间和实现分支型方法这些用途,这些情况下,耦合不是什么问题;

2)有时某些更高级的模式会更符合任务的需要。与“懒汉式”加载单例相比,虚拟代理能给予你对实例化方式更多的控制权。也可以用一个真正的对象工厂来取代分支型单例。

6、   参考文档

1)《JavaScript设计模式》 Ross HarmesDustin Dial著,谢廷晟 译,人民邮电出版社出版

2)《单例模式_百度百科》:

http://baike.baidu.com/view/1859857.htm

 

 

posted on 2011-11-07 13:14 阿蜜果 阅读(3074) 评论(3)  编辑  收藏 所属分类: JavascriptDesign Pattern


FeedBack:
# re: 蜜果私塾:在JavaScript中使用单例模式
2011-11-09 20:16 | 博洋家纺
有时某些更高级的模式会更符合任务的需要。与“懒汉式”加载单例相比,虚拟代理能给予你对实例化方式更多的控制权。也可以用一个真正的对象工厂来取代分支型单例。
  回复  更多评论
  
# re: 蜜果私塾:在JavaScript中使用单例模式
2011-11-10 08:10 | tbw
不错 学习了   回复  更多评论
  
# re: 蜜果私塾:在JavaScript中使用单例模式
2011-11-10 14:47 | tbw淘宝
尝到新东西  回复  更多评论
  

只有注册用户登录后才能发表评论。


网站导航:
 
<2011年11月>
303112345
6789101112
13141516171819
20212223242526
27282930123
45678910

      生活将我们磨圆,是为了让我们滚得更远——“圆”来如此。
      我的作品:
      玩转Axure RP  (2015年12月出版)
      

      Power Designer系统分析与建模实战  (2015年7月出版)
      
     Struts2+Hibernate3+Spring2   (2010年5月出版)
     

留言簿(262)

随笔分类

随笔档案

文章分类

相册

关注blog

积分与排名

  • 积分 - 2286201
  • 排名 - 3

最新评论

阅读排行榜

评论排行榜