WEB开发网
开发学院网页设计JavaScript Javascript乱弹设计模式系列(6) - 单件模式(Sing... 阅读

Javascript乱弹设计模式系列(6) - 单件模式(Singleton)

 2010-09-14 13:38:10 来源:WEB开发网   
核心提示:本文示例源代码或素材下载 概述 在软件系统中,总有一些类只能或者必须产生一个实例对象,Javascript乱弹设计模式系列(6) - 单件模式(Singleton),比如线程池,缓存,MooTools等等,都包含着大量单件模式的应用,注册表等等;对于这种类,如果产生多个实例对象

本文示例源代码或素材下载

概述

在软件系统中,总有一些类只能或者必须产生一个实例对象,比如线程池,缓存,注册表等等;对于这种类,如果产生多个实例对象,就会出现各种异常状况;对于这种对象只要创建一次并且分配一次内存空间即可,所以这里也有个问题,对象所分配的内存空间的消耗,对于长期不使用的对象,这就产生资源浪费,所以利用单件模式,也可以按照需要来创建对象。

定义

单件模式确保一个类只有一个实例,并且提供一个全局访问点。

类图

Javascript乱弹设计模式系列(6) - 单件模式(Singleton)

分析

首先我们考虑到传统的编程语言如C#,单件模式中设置一个静态变量,可以这样表示:

publicsealedclassSingleton
{
  staticSingletoninstance=null;
  //
}

Singleton的构造函数设置为私有,防止Singleton多次实例化,这样就可以有以下的静态方法:

publicstaticSingletonInstance
{
  get
  {
    if(instance==null)
    {
      instance=newSingleton();
    }
    returninstance;
  }
}

这样只有在第一次实例化的时候,才创建对象;通过静态方法,得到唯一实例;

这个是C#中最简单的单件模式写法。而Javascript作为弱类型语言,有着它独特的地方,现在我就来介绍Javascript单件模式的几种形式:

1. 最基本的单件模式

varLoginUser={
  name:"匿名用户",
  sex:"保密",
  setName:function(name){
    this.name=name;
  },
  setSex:function(sex){
    this.sex=sex;
  },
  getUserInfo:function(){
    return"用户名:"+this.name+";性别:"+this.sex;
  }
}

这里定义了一个对象(LoginUser),对象中包含了各种属性(name,sex)和方法(setName,setSex,getUserInfo);

这样我新建一个HTML页面:

<scripttype="text/javascript">
//
window.onload=function(){
  alert(LoginUser.getUserInfo());
  LoginUser.setName("Leepy");
  LoginUser.setSex("男");
  //alert(LoginUser.getUserInfo());
}
functiontest(){
  alert(LoginUser.getUserInfo());
}
</script>
<inputtype="button"value="test"onclick="test();"/>

可以发现,界面初始化时弹出的警告框为“用户名:匿名用户;性别:保密”,通过setName和setSex方法之后,点击按钮后弹出的警告框为“用户名:Leepy;性别:男”,说明在不同的方法作用域下,LoginUser保持着修改后的状态,因此LoginUser在页面中就保持着单一的状态。

我想大家一定也听过prototype的JS框架了吧(http://www.prototypejs.org),最新版本为(http://www.prototypejs.org/assets/2008/9/29/prototype-1.6.0.3.js),实际上在它的文件中包含着很多这样类似的代码,比如从文件一开头就可以发现:varPrototype={
 Version:'1.6.0.3',
 Browser:{
  IE:  !!(window.attachEvent&&
   navigator.userAgent.indexOf('Opera')===-1),
  Opera: navigator.userAgent.indexOf('Opera')>-1,
  WebKit:navigator.userAgent.indexOf('AppleWebKit/')>-1,
  Gecko: navigator.userAgent.indexOf('Gecko')>-1&&
   navigator.userAgent.indexOf('KHTML')===-1,
  MobileSafari:!!navigator.userAgent.match(/Apple.*Mobile.*Safari/)
 },
 BrowserFeatures:{
  XPath:!!document.evaluate,
  SelectorsAPI:!!document.querySelector,
  ElementExtensions:!!window.HTMLElement,
  SpecificElementExtensions:
   document.createElement('div')['__proto__']&&
   document.createElement('div')['__proto__']!==
    document.createElement('form')['__proto__']
 },
 ScriptFragment:'<script[^>]*>([Ss]*?)</script>',
 JSONFilter:/^/*-secure-([sS]*)*/s*$/,
 emptyFunction:function(){},
 K:function(x){returnx}
};
//以下是它的使用
if(Prototype.Browser.MobileSafari)
 Prototype.BrowserFeatures.SpecificElementExtensions=false;

可以看出,Version、Browser、BrowserFeatures、ScriptFragment、JSONFilter作为Prototype对象的属性,而emptyFUnction、K作为Prototype对象的方法;因此它就是一个最基本的单件模式。

由于Javascript的语言特性,可以在后期动态添加,删除,修改属性:

如:LoginUser.age = 24; 那么LoginUser对象便增加了age属性;而如:delete LoginUser.name; 那么LoginUser对象就删除了name属性;而如:LoginUser.name = "cnblogs"; 那么LoginUser对象的“私有”属性不需要通过setName的“公有”方法仍然能够做出修改。

因为根据设计模式原则:对扩展开放而对修改关闭,显然违背了该条准则。为了防止这种情况的发生,到时候会引入闭包的方式,稍后会说明。

2. 命名空间的单件模式

命名空间可以很好地划分 属性和方法 的归属,以及可以防止 属性和方法 被轻易的修改,通过访问各自的命名空间得到对应我们想要的 属性和方法。这里还是以上面的LoginUser为例:

varLoginUser={
  name:"匿名用户",
  sex:"保密",
  setName:function(name){
    this.name=name;
  },
  setSex:function(sex){
    this.sex=sex;
  },
  getUserInfo:function(){
    return"用户名:"+this.name+";性别:"+this.sex;
  }
}
LoginUser.Mother={
  name:"母亲姓名",
  career:"职位",
  setName:function(name){
    this.name=name;
  },
  setCareer:function(career){
    this.career=career;
  },
  getUserInfo:function(){
    returnLoginUser.name+"的母亲名字:"+this.name+";职业:"+this.career;
  }
}

从代码中看出,这里我把LoginUser作为“命名空间”,而LoginUser.Mother作为它的一个“全局变量”,这样做的好处可以防止LoginUser的属性和方法被轻易地覆盖,通过LoginUser.××××,以致于LoginUser.××××.××××(如LoginUser.Mother.Brother)来划分 属性和方法 的归属,如LoginUser中的name属性和LoginUser.Mother中的name属性是区分开来的。

这样我新建一个HTML页面:

<script type="text/javascript">
//
window.onload = function() {
  LoginUser.setName("Leepy");
  LoginUser.Mother.setName("admin");
  LoginUser.Mother.setCareer("农民");
  alert(LoginUser.Mother.getUserInfo());
}
</script>

可以得到下面的弹出框:

Javascript乱弹设计模式系列(6) - 单件模式(Singleton)

LoginUser和LoginUser.Mother的name属性已经区分开来了。

在prototype.js文件中也用到命名空间的单件模式:var Class = {
 create: function() {
  var parent = null, properties = $A(arguments);
  if (Object.isFunction(properties[0]))
   parent = properties.shift();  function klass() {
   this.initialize.apply(this, arguments);
  }
  
  Object.extend(klass, Class.Methods);
  klass.superclass = parent;
  klass.subclasses = [];
  
  if (parent) {
   var subclass = function() { };
   subclass.prototype = parent.prototype;
   klass.prototype = new subclass;
   parent.subclasses.push(klass);
  }
  
  for (var i = 0; i < properties.length; i++)
   klass.addMethods(properties[i]);
  
  if (!klass.prototype.initialize)
   klass.prototype.initialize = Prototype.emptyFunction;
  
  klass.prototype.constructor = klass;
  
  return klass;
 }
};
  
Class.Methods = {
 addMethods: function(source) {
  var ancestor  = this.superclass && this.superclass.prototype;
  var properties = Object.keys(source);
  
  if (!Object.keys({ toString: true }).length)
   properties.push("toString", "valueOf");
  
  for (var i = 0, length = properties.length; i < length; i++) {
   var property = properties[i], value = source[property];
   if (ancestor && Object.isFunction(value) &&
     value.argumentNames().first() == "$super") {
    var method = value;
    value = (function(m) {
     return function() { return ancestor[m].apply(this, arguments) };
    })(property).wrap(method);
  
    value.valueOf = method.valueOf.bind(method);
    value.toString = method.toString.bind(method);
   }
   this.prototype[property] = value;
  }
  
  return this;
 }
};

这里实际上Class.create实现的是类的继承,具体这里我就不再阐述了,大家可以查看prototype官方的Api文档。

3. 闭包方式的单件模式

如果要得到真正意义上的“私有”成员,那么闭包方式是构造单件模式的一种选择。通过闭包的方式,只暴露一些可以公开的方法或者属性,而私有成员只在内部实现操作,而所有的属性和方法只需要实例化一次。现在开始继续看LoginUser的例子,闭包方式单件模式(左)对比第1条基本单件模式(右)的例子:

varLoginUser=(function(){
  var_name="匿名用户";
  var_sex="保密";
  return{
    setName:function(name){
      _name=name;
    },
    setSex:function(sex){
      _sex=sex;
    },
    getUserInfo:function(){
      return"用户名:"+_name+";性别:"+_sex;
    },
    getName:function(){
      return_name;
    }
  };
})();

varLoginUser={
  _name:"匿名用户",
  _sex:"保密",
  setName:function(name){
    this._name=name;
  },
  setSex:function(sex){
    this._sex=sex;
  },
  getUserInfo:function(){
    return"用户名:"+this._name+";性别:"+this._sex;
  }
}

可以发现,闭包方式将公共的方法放在return { ... }中,而属性_name和_sex做为参数传入return { ... }中;

现在两种方式都实现一下代码,测试一下:

window.onload=function(){
  LoginUser.setName("Leepy");
  LoginUser.setSex("男");
  alert(LoginUser._name);
}

可以得到闭包方式单件模式(左)对比第1条基本单件模式(右)如下两个结果:

Javascript乱弹设计模式系列(6) - 单件模式(Singleton)

Javascript乱弹设计模式系列(6) - 单件模式(Singleton) 

可以看出闭包方式的LoginUser无法得到_name的值,而基本方式的LoginUser可以得到_name的值;

这进一步说明了闭包方式的_name已经成为“私有”成员属性了。而如果要得到_name的值,只有通过公开方法或者公开属性来获得,如下:

return { // 注意这里的“{”号不能够换行到下一行,不然浏览器提示错误
  getName : function() {
    return _name;
  }
}

这样子,alert(LoginUser.getName()); 就可以显示正确的值了。

继续看prototype.js文件中,其实也用到闭包方式的单件模式:var Hash = Class.create(Enumerable, (function() { function toQueryPair(key, value) {
  if (Object.isUndefined(value)) return key;
  return key + '=' + encodeURIComponent(String.interpret(value));
 }
  
 return {
  initialize: function(object) {
   this._object = Object.isHash(object) ? object.toObject() : Object.clone(object);
  },
  
  _each: function(iterator) {
   for (var key in this._object) {
    var value = this._object[key], pair = [key, value];
    pair.key = key;
    pair.value = value;
    iterator(pair);
   }
  },
  
  set: function(key, value) {
   return this._object[key] = value;
  },
  
  get: function(key) {
   // simulating poorly supported hasOwnProperty
   if (this._object[key] !== Object.prototype[key])
    return this._object[key];
  },
  
  unset: function(key) {
   var value = this._object[key];
   delete this._object[key];
   return value;
  },
  
  toObject: function() {
   return Object.clone(this._object);
  },
  
  keys: function() {
   return this.pluck('key');
  },
  
  values: function() {
   return this.pluck('value');
  },
  
  index: function(value) {
   var match = this.detect(function(pair) {
    return pair.value === value;
   });
   return match && match.key;
  },
  
  merge: function(object) {
   return this.clone().update(object);
  },
  
  update: function(object) {
   return new Hash(object).inject(this, function(result, pair) {
    result.set(pair.key, pair.value);
    return result;
   });
  },
  
  toQueryString: function() {
   return this.inject([], function(results, pair) {
    var key = encodeURIComponent(pair.key), values = pair.value;
  
    if (values && typeof values == 'object') {
     if (Object.isArray(values))
      return results.concat(values.map(toQueryPair.curry(key)));
    } else results.push(toQueryPair(key, values));
    return results;
   }).join('&');
  },
  
  inspect: function() {
   return '#<Hash:{' + this.map(function(pair) {
    return pair.map(Object.inspect).join(': ');
   }).join(', ') + '}>';
  },
  
  toJSON: function() {
   return Object.toJSON(this.toObject());
  },
  
  clone: function() {
   return new Hash(this);
  }
 }
})());

Class.create的第二个参数就是闭包方式的单件对象,这里的作用是将Hash对象继承于Enumerable类,并且包含了单件对象的公开方法如set,get,keys,values等等操作获取散列键值的方法。具体这里我就不再阐述了,大家可以查看prototype官方的Api文档。

4. 延迟加载的单件模式

上面介绍的各种方式都是在建立对象的时候,对象内部的成员都已经加载完毕,如果对于资源占用多的脚本,在不需要的时候,这对于内存造成了极大的浪费,所以要考虑一种方式将成员实例化推迟到需要调用对象的时候,也就是叫做延迟加载。

继续以LoginUser的例子作为演示:

var LoginUser = (function(){
  var uniqueInstance;
  var _name;
  var _sex;
  function constructor(){
    _name = "匿名用户";
    _sex = "保密";
    
    return {
      setName : function(name){
      _name = name;
      },
      setSex : function(sex){
        _sex = sex;
      },
      getUserInfo : function(){
        return "用户名:" + _name + ";性别:" + _sex;
      },
      getName : function(){
        return _name;
      }
    };
  }
  
  return {
    getInstance : function() {
      if(uniqueInstance == null)
      {
        uniqueInstance = constructor();
      }
      return uniqueInstance;
    }
  }
})();

可以看到,我这里添加了一个私有方法constructor(),并且由它来公开成员方法;

再则,当第一次调用getInstance方法的时候,调用contructor方法,并且对于成员属性_name和_sex进行初始化,说明通过调用getInstance方法才对属性进行初始化,平时不进行初始化,constructor()返回了一个uniqueInstance的对象,由uniqueInstance对象负责该单件对象的公开方法的操作。

然后新建一个HTML页面:

window.onload = function() {
  var user1 = LoginUser.getInstance();
  user1.setName("Leepy");
  user1.setSex("男");
  alert(user1.getUserInfo());//用户名:Leepy;性别:男
  
  var user2 = LoginUser.getInstance();
  alert(user1 == user2); //user1和user2共享同一块内存空间,为true
}

只有通过LoginUser.getInstance()后,LoginUser才进行成员初始化,而方法返回的对象共享同一块的内存空间。这就是延迟加载单件模式的工作原理。

5. 其他

另外上次园里一位朋友(winter-cn)发我的一个单件设计模式的文章,觉得挺不错,它也是利用了“匿名”函数的特征构建了单件模式,我这里把代码贴出来一下:

<script>
(function(){
  //instance declared
  //SingletonFactory Interface
  SingletonFactory = {
    getInstance : getInstance
  }
  
  //private classes
  function SingletonObject()
  {
    SingletonObject.prototype.methodA = function()
    {
      alert('methodA');
    }
    SingletonObject.prototype.methodB = function()
    {
      alert('methodB');
    }
    SingletonObject.instance = this;
  }
  
  //SingletonFactory implementions
  function getInstance()
  {
    if(SingletonObject.instance == null)
      return new SingletonObject();
      
    else
      return SingletonObject.instance;
  }
  
})();
  
var instA = null;
try
{
alert("试图通过new SingletonObject()构造实例!");
instA = new SingletonObject();
}
catch(e){alert("SingletonObject构造函数不能从外部访问,系统抛出了异常!");}
  
instA = SingletonFactory.getInstance(); //通过Factory上定义的静态方法获得
var instB = SingletonFactory.getInstance();
instA.methodA();
instB.methodA();
  
alert(instA == instB); //成功
  
var instC = null;
try
{
alert("试图通过new SingletonObject()构造实例!");
instC = new SingletonObject();
}
catch(e){alert("SingletonObject构造函数不能从外部访问,系统抛出了异常!");}
</script>

总结

该篇文章用Javascript来设计单件模式的几种方式,在许多优秀的JS开源框架中,如prototype,MicrosoftAjaxLibrary,jQuery,MooTools等等,都包含着大量单件模式的应用,所以单件模式在Javascript中是很重要的一个模式。

相关系列文章:

Javascript乱弹设计模式系列(6) - 单件模式(Singleton)

Javascript乱弹设计模式系列(5) - 命令模式(Command)

Javascript乱弹设计模式系列(4) - 组合模式(Composite)

Javascript乱弹设计模式系列(3) - 装饰者模式(Decorator)

Javascript乱弹设计模式系列(2) - 抽象工厂以及工厂方法模式(Factory)

Javascript乱弹设计模式系列(1) - 观察者模式(Observer)

Javascript乱弹设计模式系列(0) - 面向对象基础以及接口和继承类的实现

Tags:Javascript 乱弹 设计模式

编辑录入:爽爽 [复制链接] [打 印]
赞助商链接