免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 2310 | 回复: 1
打印 上一主题 下一主题

JavaScript设计模式-单例模式、模块模式 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2012-02-17 23:54 |只看该作者 |倒序浏览
JavaScript设计模式-单例模式、模块模式










之前在《JavaScript小特性-面向对象》里面介绍过JavaScript面向对象的特性了,有了面向对象之后,自然就会想——那是不是还有设计模式呢?由于js面向对象的方式是基于原型(prototype)的,而不是传统基于类型(class),所以js的设计模式也和经典的设计模式有些差异。



关于设计模式
先说说什么是设计模式吧。很多人都觉得“设计模式”这东西很玄乎,把Gof四人帮的《Design Patterns》奉为编程圣经,而我却觉得大可不必。设计模式说白了,就是在特定环境下解决某类常见问题的一种套路,按着这种套路去做就会很得心应手。但是,这并不代表这些设计模式是放之四海皆准,更不是说套着设计模式做的东西就一定是最好维护、性能最佳、至高无上的了。就算你不知道什么设计模式,只要思路正确,也能写出设计模式来。



有大师说,设计模式其实是对语言缺陷的一种弥补。这里有两层意思,一是说设计模式是基于语言的,因为语言存在缺陷,能力有所不足,所以需要设计模式去提供一种通用的解决方式去弥补这些不足;二是说,如果脱离具体语言及具体环境,生搬硬套设计模式甚至会是一种束缚,设计模式并非绝对正确。

了解了上面的观点之后,下面就通过js的单例模式来看看,JavaScript的设计模式是怎么回事。



单例模式(Singleton)
传统的单例模式大概是这么回事(Java版):

Js代码
  1. public class Singleton{   
  2.     private static Singleton instance;   
  3.     private int property;//某些属性   
  4.     private Singleton(){   
  5.         //通过私有的构造函数来保证只有一个实例   
  6.     }   
  7.     public static synchronized Singleton getInstance(){   
  8.         if (instance == null) {   
  9.             instance = new Singleton();   
  10.         }   
  11.         return instance;   
  12.     }   
  13.     public void method(){//某些方法}   
  14. }  

  15. public class Singleton{
  16.     private static Singleton instance;
  17.     private int property;//某些属性
  18.     private Singleton(){
  19.         //通过私有的构造函数来保证只有一个实例
  20.     }
  21.     public static synchronized Singleton getInstance(){
  22.         if (instance == null) {
  23.             instance = new Singleton();
  24.         }
  25.         return instance;
  26.     }
  27.     public void method(){//某些方法}
  28. }
复制代码
传统单例模式的特点就是利用私有构造函数来保证只有一个实例,使不同地方调用的都是同一个实例对象。然而这一点对于js来说就有点弱爆了,因为js是允许全局变量的,解决单例的办法很简单,定义一个全局对象就完事了(是不是觉得很原始,完全没什么模式可言):
Js代码
  1. var jsSingleton = {   
  2.     property:"something",   
  3.     method:function(){   
  4.         console.log('hello world');   
  5.     }   
  6. }  

  7. var jsSingleton = {
  8.     property:"something",
  9.     method:function(){
  10.         console.log('hello world');
  11.     }
  12. }
复制代码
调用也很简单,无需顾及什么类名,任何地方访问jsSingleton都是同一个对象,完全不用担心。如果你气急败坏的跟我说,你这只是饿汉式的,懒汉式单例呢?如果真的创建对象时很耗费资源,或者是你对性能洁癖到一定程度,那也只能让js东施效颦的学一下传统的方式了:

Js代码
  1. var jsLazySingleton =(function(){   
  2.     var instance;   
  3.     //利用闭包来解决私有构造函数的问题(后面有解释)   
  4.     function init (){   
  5.         return {   
  6.             property: 'some thing',   
  7.             method: function(){}   
  8.         }   
  9.     }   
  10.     return {   
  11.         getInstance :function(){   
  12.             //如果instance存在则返回,不存在则调用init()   
  13.             return instance || instance = init();   
  14.         }   
  15.     }   
  16. })();  

  17. var jsLazySingleton =(function(){
  18.     var instance;
  19.     //利用闭包来解决私有构造函数的问题(后面有解释)
  20.     function init (){
  21.         return {
  22.             property: 'some thing',
  23.             method: function(){}
  24.         }
  25.     }
  26.     return {
  27.         getInstance :function(){
  28.             //如果instance存在则返回,不存在则调用init()
  29.             return instance || instance = init();
  30.         }
  31.     }
  32. })();
复制代码
模块模式(Module)
在js的懒汉式单例模式中,其实也用到了另一种设计模式,即模块模式。在传统软件工程中,模块模式被定义为给类提供私有和公共封装的一种方法,也就是我们常说的“模块化”。在Java里面Class就是一种模块,解决了属性、方法的封装问题,它的模块模式直接就融合到语言特性里面了,因此也没什么模块模式的说法;然而对于JavaScript这种过于灵活的语言,这种最基本的私有化封装却需要用一个设计模式来解决了。



在JavaScript中,“private”是作为保留字,而不是关键字的,也就是说,JavaScript没有私有化这一功能(纠结吧)。解决解决这问题有两种方法,一是定义变量的时候在前面加上下划线“_”,也就是告诉其他开发者,不要动这个变量哟;另一种则是利用闭包。第一种方法并不是真正的私有,只是一种规范,如果要做到真正的私有,还是要用第二种方法——闭包。

我们创建一个匿名函数,然后立即运行它,此匿名函数中的所有代码都存在于一个“闭包”之中,从而得到私有性,并在特定作用域中保持可被访问。方法如下:

Js代码
  1. (function () {   
  2.     //此作用域的所有变量、函数依旧可在特定作用域中被访问   
  3. })();  

  4. (function () {
  5.     //此作用域的所有变量、函数依旧可在特定作用域中被访问
  6. })(); 先用括号把函数定义括起来,从而得到该函数对象,然后后面的括号是立即运行它。这种形式可以在很多js库中见到,例如jQuery:
  7. Js代码  
  8. (function( window, undefined ) {   
  9.     ......   
  10.     //最后一行   
  11.     window.jQuery = window.$ = jQuery;   
  12. })(window);  

  13. (function( window, undefined ) {
  14.     ......
  15.     //最后一行
  16.     window.jQuery = window.$ = jQuery;
  17. })(window);
复制代码
我们看到jQuery把window这个全局变量传进匿名函数中,然后把内部定义的jQuery赋值给了window,从而在全局作用域中都可以通过“$”符号来访问匿名函数中的内容(想了解更多关于闭包的内容,可以看看《jQuery自运行匿名函数》《闭包与作用域链》)。


弄清楚这些之后,我们就来看看js模块模式的基本样子吧:

Js代码
  1. var Module = (function () {   
  2.     var my={},   
  3.         privateVar = 8;//私有属性   
  4.     function privateFun() {//私有方法   
  5.         return ++privateVar;   
  6.     };   
  7.     my.publicVar = 1;//公共属性   
  8.     my.moduleFun = function () {//公共方法   
  9.         return privateFun();   
  10.     };   
  11.     return my;   
  12. }());   
  13. console.log(Module.publicVar);//1   
  14. console.log(Module.publicFun());//9  

  15. var Module = (function () {
  16.     var my={},
  17.         privateVar = 8;//私有属性
  18.     function privateFun() {//私有方法
  19.         return ++privateVar;
  20.     };
  21.     my.publicVar = 1;//公共属性
  22.     my.moduleFun = function () {//公共方法
  23.         return privateFun();
  24.     };
  25.     return my;
  26. }());
  27. console.log(Module.publicVar);//1
  28. console.log(Module.publicFun());//
复制代码
9 在匿名函数中我们返回了一个my变量给Module作为外部访问闭包内容的接口,除闭包内my之外的内容都得到了私有性保护,闭包的数据在Module变量的作用域中保持可以访问。


好了,模块模式解决了JavaScript私有化的问题,我们可以利用它来定义命名空间、单例、拥有私有化封装的对象等等。然而模块模式也并非尽善尽美。例如,我们定义私有、公共变量的方法是不同的,当开发过程中我们需要改变某个变量的可见性的时候,就不得不在它所有出现过的地方进行修改;并且JavaScript作为动态编译的语言,我们可以随时给对象添加属性、方法,然而我们在闭包之外定义的方法是无法直接访问私有数据的。



更高级的模块模式
虽然前面提到的模块模式对于大多数开发者来说已经足够了,但是模块模式还可以被改进的更加强大、更加易于扩展。

扩展性
之前所说的模板模式有一个限制,就是整个模板必须定义在一个文件中。曾面对一大堆代码工作的人肯定明白将它划分为多个文件的意义。好在,有个巧妙的方法来扩展我们的Modules。首先,在匿名函数的参数中导入Module,然后给它添加属性,然后再导出它。下面这个例子是在另一个文件中对上面提到的Module进行扩展(必须是全局作用域的情况):

Js代码
  1. var Module = (function (my) {   
  2.     my.anotherFun = function () {   
  3.         // do someting...   
  4.     };   
  5.     return my;   
  6. }(Module||{}));//注意,这里还有个“{}”  

  7. var Module = (function (my) {
  8.     my.anotherFun = function () {
  9.         // do someting...
  10.     };
  11.     return my;
  12. }(Module||{}));//
复制代码
注意,这里还有个“{}” 我们重新用var关键字定义Module以保证一致性,并且如果之前Module没有被创建的话,在这里会自动的被创建为空对象“{}”。当这段代码运行结束之后,我们的Module又有了一个新的公共方法Module.anotherFun()。每个此扩展的Module都有自己独有的私有属性、方法。由于导入的时候可以自动创建,这些包含Module定义的文件之间可以以任意顺序加载(如果存在依赖关系,必须按次序加载的话,那你按次序就好了)。
跨文件私有属性共享
目前的多文件扩展Module还有个一严重的限制,那就是每个文件的Module都只保持自己的私有状态,无法访问其他文件的私有属性。当然,这也可以被解决。下面是解决多文件私有属性共享的一个方法:

Js代码
  1. var MODULE = (function () {   
  2.     //将所有的私有属性、方法都定义在_private对象中   
  3.     //每个扩展Module都可以通过my._private来访问   
  4.     var my = {},   
  5.     _private = my._private = {},   
  6.    
  7.     _seal = function (){   
  8.         //密封,删除所有私有数据的可访问性   
  9.         delete my._private;   
  10.     },   
  11.     _unseal = function (){   
  12.         //解封,让私有数据重新可访问   
  13.         my._private = _private;   
  14.     };   
  15.    
  16.     my.extend = function(otherModules){   
  17.         //必须通过此方法来添加扩展Module文件   
  18.         _unseal();   
  19.         //add other modules   
  20.         _seal();//异步调用,此处只是示意,真正的代码并非如此   
  21.     }   
  22.     return my;   
  23. }());  

  24. var MODULE = (function () {
  25.     //将所有的私有属性、方法都定义在_private对象中
  26.     //每个扩展Module都可以通过my._private来访问
  27.     var my = {},
  28.     _private = my._private = {},

  29.     _seal = function (){
  30.         //密封,删除所有私有数据的可访问性
  31.         delete my._private;
  32.     },
  33.     _unseal = function (){
  34.         //解封,让私有数据重新可访问
  35.         my._private = _private;
  36.     };

  37.     my.extend = function(otherModules){
  38.         //必须通过此方法来添加扩展Module文件
  39.         _unseal();
  40.         //add other modules
  41.         _seal();//异步调用,此处只是示意,真正的代码并非如此
  42.     }
  43.     return my;
  44. }());
复制代码
上面的Module文件必须第一个被加载,然后利用Module.extend来加载其他的扩展。my.extend实际上是通过类似Labjs的工具来并行加载其他的Module。每个扩展Module都通过my._private来传递私有性,并且都需要将传来的my._private保存为自己的私有属性,所有对my._private的修改都将反应到其他扩展Module中去。此方法参考于Ben Cherry的《JavaScript Module Pattern: In-Depth》。



小结一下
看完了JavaScript的单例模式和模块模式,是不是有点感叹与JavaScript设计模式的不伦不类呢?这些就是语言特性的体现,设计模式是依赖于语言特性的,是对语言能力的一种补充。JavaScript的设计模式并非不伦不类,很多JavaScript也有很多优雅的设计模式,可以让传统设计模式相形见绌的。

接下来还打算写几篇关于JavaScript设计模式的文章,敬请期待。

论坛徽章:
0
2 [报告]
发表于 2012-02-17 23:55 |只看该作者
谢谢分享
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP