JavaScript设计模式-单例模式、模块模式(转载 学习中。。。。)

(转载地址:http://technicolor.iteye.com/blog/1409656)

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

关于设计模式

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

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

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

单例模式(Singleton)

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

  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. }
 传统单例模式的特点就是利用私有构造函数来保证只有一个实例,使不同地方调用的都是同一个实例对象。然而这一点对于js来说就有点弱爆了,因为js是允许全局变量的,解决单例的办法很简单,定义一个全局对象就完事了(是不是觉得很原始,完全没什么模式可言):
  1. var jsSingleton = {
  2. property:"something",
  3. method:function(){
  4. console.log('hello world');
  5. }
  6. }

调用也很简单,无需顾及什么类名,任何地方访问jsSingleton都是同一个对象,完全不用担心。如果你气急败坏的跟我说,你这只是饿汉式的,懒汉式单例呢?如果真的创建对象时很耗费资源,或者是你对性能洁癖到一定程度,那也只能让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. })();
  

模块模式(Module)

在js的懒汉式单例模式中,其实也用到了另一种设计模式,即模块模式。在传统软件工程中,模块模式被定义为给类提供私有和公共封装的一种方法,也就是我们常说的“模块化”。在Java里面Class就是一种模块,解决了属性、方法的封装问题,它的模块模式直接就融合到语言特性里面了,因此也没什么模块模式的说法;然而对于JavaScript这种过于灵活的语言,这种最基本的私有化封装却需要用一个设计模式来解决了。

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

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

  1. (function () {
  2. //此作用域的所有变量、函数依旧可在特定作用域中被访问
  3. })();
 先用括号把函数定义括起来,从而得到该函数对象,然后后面的括号是立即运行它。这种形式可以在很多js库中见到,例如jQuery:
  1. (function( window, undefined ) {
  2. ......
  3. //最后一行
  4. window.jQuery = window.$ = jQuery;
  5. })(window);
 我们看到jQuery把window这个全局变量传进匿名函数中,然后把内部定义的jQuery赋值给了window,从而在全局作用域中都可以通过“$”符号来访问匿名函数中的内容(想了解更多关于闭包的内容,可以看看《jQuery自运行匿名函数》《闭包与作用域链》)。
 

弄清楚这些之后,我们就来看看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
 在匿名函数中我们返回了一个my变量给Module作为外部访问闭包内容的接口,除闭包内my之外的内容都得到了私有性保护,闭包的数据在Module变量的作用域中保持可以访问。
 

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

更高级的模块模式

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

扩展性

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

  1. var Module = (function (my) {
  2. my.anotherFun = function () {
  3. // do someting...
  4. };
  5. return my;
  6. }(Module||{}));//注意,这里还有个“{}”
 我们重新用var关键字定义Module以保证一致性,并且如果之前Module没有被创建的话,在这里会自动的被创建为空对象“{}”。当这段代码运行结束之后,我们的Module又有了一个新的公共方法Module.anotherFun()。每个此扩展的Module都有自己独有的私有属性、方法。由于导入的时候可以自动创建,这些包含Module定义的文件之间可以以任意顺序加载(如果存在依赖关系,必须按次序加载的话,那你按次序就好了)。

跨文件私有属性共享

目前的多文件扩展Module还有个一严重的限制,那就是每个文件的Module都只保持自己的私有状态,无法访问其他文件的私有属性。当然,这也可以被解决。下面是解决多文件私有属性共享的一个方法:

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

上面的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设计模式的文章,敬请期待。

上一篇:UVA 11294 Wedding(2-sat)


下一篇:Linux select TCP并发服务器与客户端编程