JavaScript设计模式——单体模式

一:单体模式简介:

  是什么:将代码组织为一个逻辑单元,这个单元中的代码通过单一的变量进行访问。只要单体对象存在一份实例,就可以确信自己的所有代码使用的是同样的全局资源。

  用途:1.用来划分命名空间,减少网页中全局变量的数目。

    2.在分支技术中用来封装浏览器之间的差异。

    3.单体对象创建的命名空间可以快速清除全局变量。

    4.增强模块性

  关于单体模式的好坏,等你看完所有的讲解之后再告诉你哦.......

二:单体结构:

1.最简单的单体就是一个对象字面量。

1     var ProductTools={
2         findProduct: function(){
3             //.........
4         },
5         //其他类似的方法.....
6     }
7 
8     var resetProduct = $('rest-product-button');
9     var findProduct = $('find-product-button');

 ProductTools中的方法不会被外面的所覆盖,它被装在自己创建的命名空间中。(金钟罩,铁布衫,哈哈)

 接着可以把命名空间进行进一步的分割。为了避免与其他库代码冲突,可以定义一个用来包含自己所有代码的全局对象: 

 1     var GaintCrop = {};
 2     GaintCrop.Common = {
 3         //....拥有普通方法的单体,被用于所有的对象和方法。
 4     }
 5     GaintCrop.ErrorCodes = {
 6         //....用来储存数据
 7     }
 8     GaintCrop.PageHandler = {
 9         //....拥有特殊和私有页面方法的单体
10     }

2.用作特定网页专用代码的包装器的单体:

有些js代码用于多个页面,而有的只用于某个特定的网页,所以最好把这两种代码包装在自己的单体对象中。

接下来,举一个例子吧。常用的表单提交:

下面的单体会查找并劫持一个特定表单的提交:

 1     GaintCrop.RegPage = {
 2         FORM_ID : 'reg-form',//要提交表单的ID
 3         OUTPUT_ID : 'reg-results',//要显示提交成功后结果的元素的ID
 4 
 5         //....表单的处理方法....
 6         handleSubmit: function(e){
 7             e.preventDafault();//取消事件的默认动作
 8 
 9             var data = {};
10             var inputs = GaintCrop.RegPage.formEl.getElementsByTagName('input');
11 
12             //取出表单中input框输入的值
13             for(var i= 0, len = inputs.length; i < len; i++){
14                 data[inputs[i].name] = inputs[i].value;
15             }
16             //把表单中的信息提交的服务器
17             GaintCrop.RegPage.sendRegistration(data);
18         },
19         sendRegistration: function(){
20             //发送请求到服务器(调用ajax),然后在成功函数里面调用
21             //......
22         },
23         displayResult: function(){
24             //显示服务器返回的data数据到页面中....
25             GaintCrop.RegPage.outputEl.innerHTML = response;
26         },
27         //初始化方法
28         init: function(){
29             //取出表单元素和要显示提交成功后结果的元素
30             GaintCrop.RegPage.formEl = $(GaintCrop.RegPage.FORM_ID);
31             GaintCrop.RegPage.outputEl = $(GaintCrop.RegPage.OUTPUT_ID);
32 
33             //劫持表单的提交
34             //参数:(dom对象,事件类型,触发函数)
35             addEvent(GaintCrop.RegPage.formEl, 'submit', GaintCrop.RegPage.handleSubmit)
36         }
37     };
38 
39     //页面加载完成后调用 RegPage 的初始化方法。
40     addLoadEvent(GaintCrop.RegPage.init);

看到上面的代码是不是讲的很详细呢,是就点个头,表示一下赞同,真的是....

对于 GaintCrop 前面讲过了,在这里使用时它应该是已经作为一个空的对象字面量存在的(你存在....我存在...... - _ -)。为了以防万一可以添加如下代码:

1     var GaintCrop = window.GaintCrop || {};

3.拥有私用成员的单体:

(1)用下划线表示

下面举一个例子:构造一个单体,干嘛用呢》....用来把string转换成数组

 1     GaintCrop.DataParse = {
 2         //私有方法
 3         _stripWhitespace: function(str){
 4             return str.replace(/s+/, '');//去掉空格
 5         },
 6         _stringSplit: function(str, delimiter){
 7             return str.split(delimiter);//切割string,规则是delimiter
 8         },
 9 
10         //公用方法
11         //stripWS 表示是否删除所有空格(一个布尔型的值)
12         stringToArray: function(str, delimiter, stripWS){
13             if(stripWS){
14                 str = this._stripWhitespace(str);
15             }
16             var outputArray = this._stringSplit(str, delimiter);
17             return outputArray;
18         }
19     };

上面的两个方法作为私有方法提供给stringToArray方法使用。

仅仅是用下划线表示吗?......我去.........就是说带下划线就是私用的了 0.0 。当如果有一天你觉得某个带下划线的方法可以不用存在了,你就删了它,然后也不会对外面有影响,因为,一般也不会有人去调用你带下划线的方法,除非他S....B....  .真心不会有人这么调用。有的话,公司可以把它给辞掉了....(0.0有点严重,ha)

(2)使用闭包:

现在我们用一个在定义后立即执行的函数创建单体。

这个包装函数创建了一个可以用来添加真正的私用成员的闭包

 1     GaintCrop.DataParser = (function () {
 2         //私有属性
 3         var whitespaceRegex = /s+/;
 4 
 5         //私有方法
 6         function stripWhitespace(str){
 7             return str.replace(whitespaceRegex, '');
 8         }
 9         function stringSplit(str, delimiter){
10             return str.split(delimiter);
11         }
12 
13         return {
14             //公有方法
15             stringToArray: function(str, delimiter, stripWS){
16                 if(stripWS){
17                     str = stripWhitespace(str);
18                 }
19                 var outputArray = stringSplit(str, delimiter);
20                 return outputArray;
21             }
22         }
23     })();

GaintCrop.DataParser 获得的并不是一个函数,而是这个立即执行的方法返回的一个对象,这样返回的对象中的方法因为是在那个立即执行的函数中定义的,so......就可以调用所谓的私有方法和属性咯......这个闭包的概念不懂的去我前几节博客看一下吧....

4.惰性实例化:

什么是惰性实例化:这个吗>......前面讲了这么多...有一个共性,单体对象都是在脚本加载时被创建出来。对于资源密集型的或者配置开销大的单体,更合理的做法是将其实例化推迟到需要使用的时候。这种技术被称为惰性加载...lazy loading ,它最常用于那些必须加载大量数据的单体。而那些被用作命名空间、特定网页专用代码包装器或组织相关实用的方法的工具的单体最好还是立即实例化。

一个常规的单体转化成惰性的要分几步?(大象装冰箱分几步?....)

首先把单体的所有代码移到一个叫constructor的函数中:

 1         function constructor(){
 2             //所有常规的单体的代码:
 3             //私有方法和属性
 4             var privateAttribute1 = false;
 5             var privateAttribute2 = "123";
 6 
 7             function privateMehtod1(){
 8                 //>....
 9             }
10             function privateMehtod2(){
11                 //>....
12             }
13 
14             return {
15                 //公有属性和方法:
16                 publicAttribute1: true;
17                 publicAttribute2: 10;
18                 publicMethod1: function(){
19                     //....
20                 };
21                 publicMethod2: function(){
22                     //....
23                 }
24             }
25

这个方法外部不能访问,通过一个公用方法 getInstance 来访问: 

 1     GaintCrop.Singleto = (function(){
 2         var uniqueInstance;//用来判断单体是否实例化的私有属性
 3 
 4         function constructor(){
 5             //所有常规的单体的代码:
 6             //.......
 7         }
 8         return {
 9             getInstance: function(){
10                 if(!uniqueInstance){ //如果单体没被实例话,就实例化单体
11                     uniqueInstance = constructor();
12                 }
13                 return uniqueInstance;
14             }
15         }
16     })();

这样转换完后,要调用单体的公有方法,需要改变调用方式:GaintCrop.Singleto.getInstance().publicMethod1();

哎呀,调用个方法要写这么长。烦死.......(这就是惰性加载单体的缺点之一)。

 5.分支:

分支是一种用来把浏览器间的差异封装到运行期间进行设置的动态方法中的技术。(有点抽象吗?....看看下面的例子来理解吧)

举个例子,假如我们要创建一个返回XHR对象的方法,这种XHR对象在多数浏览器中石XMLHttpRequest类的实例,而在IE早期版本中则是某种ActiveX类的实例。这样一个方法通常会进行某种浏览器嗅探或者对象探测。如果不用分支技术,那么每次调用这个方法时,所有那些浏览器嗅探代码都要再次运行。要是这个方法的调用很频繁,这样做会严重缺乏效率。所有最好的办法是脚本加载时一次性地确定针对浏览器的代码。

用分支技术创建XHR对象:

 1     var simpleXhrFactory = (function () {
 2         //三个分支:
 3         var standard = {
 4             createXhrObject: function(){
 5                 return new XMLHttpRequest();
 6             }
 7         };
 8         var activeXNew = {
 9             createXhrObject: function(){
10                 return new ActiveXObject('Msxml2.XMLHTTP');
11             }
12         };
13         var activeXOld = {
14             createXhrObject: function(){
15                 return new ActiveXObject('Microsoft.XMLHTTP');
16             }
17         };
18 
19         var testObject;
20         try{
21             testObject = standard.createXhrObject();
22             return standard;
23         }
24         catch(e){
25             try{
26                 testObject = activeXNew.createXhrObject();
27                 return activeXNew;
28             }
29             catch(e){
30                 try{
31                     testObject = activeXOld.createXhrObject();
32                     return activeXOld;
33                 }
34                 catch(e){
35                     throw new Error('环境中未发现XHR对象....')
36                 }
37             }
38         }
39     })();

这个单体可以用来生成XHR对象,使用这个API,只需要调用SimpleXhrFactory.createXhrObject() 就能得到适合特定的运行时环境的XHR对象。

这个单体创建了三个分支,通过一个变量testObject来测试是否是浏览器支持的XHR.

三:单体模式的利弊:

好处:

  1.组织作用:单体模式的主要好处在于它对代码的组织作用。把相关方法和属性组织在一个不会被多次实例化的单体中,可以使代码的调试和维护变得更轻松哦......

  2.节约内存:同第一条所讲,单体模式只会被实例化一次,节约内存。

  3.防止被误改:把方法包裹在单体中,可以防止它们被其他程序猿误改。

  单体模式的一些高级变体可以在开发后期用于对脚本进行优化,提高性能:

    使用惰性实例化技术,可以直到需要一个对象的时候才创建它,从而减少那些不需要它的用户承受的不必要的内存消耗(还可能包括带宽消耗)。

    分支技术可以用来创建高效的方法,不用管浏览器或者环境的兼容性。通过根据运行时的条件确定赋给单体变量的对象字面量,你可以创建出为特定环境量身定制的方法,这种方法不会在每次调用时都浪费时间去检查运行环境。

坏处:

  由于单体模式提供的是一种单点访问,所以它有可能导致模块间的强耦合。又因为这种强耦合导致它不利于单元测试。(你无法单独测试一个调用了来自单体的方法的类,只能把它和那个单体作为一个单元一起测试....)

  

原文地址:https://www.cnblogs.com/xinxingyu/p/4803117.html