保证一个类仅有一个实例,并提供一个访问它的全局访问点。
单体的定义:单体是一个用来划分命名空间并将一批相关方法和属性组织在一起的对象,它可以被实例化,但只能被实例化一次。
单体模式是JavaScript中最基本但又最有用的模式之一。
单体类在JavaScript中有许多用途。用来划分命名空间,以减少网页中全局变量的数量;用分支的技术来封装浏览器之间的差异;借助单体模式,你可以把代码组织得更为一致,从而使其容易阅读和维护。
单体的基本结构
/* Basic Singleton */ var Singleton = { attr1: true, attr2: 10, method1: function() { //... }, method2: function(arg) { //... } };
最简单的单体实际上就是一个对象字面量,它把一批有一定关联的方法和属性组织在一起。
惰性实例化
惰性加载是另一种单例的构造。前面所讲的单体模式都是在脚本加载时被创建出来。这对于资源密集型的或配置开销大的单体不是很好。
合理做法:将实例化推迟到需要使用它的时候。
惰性加载常用于那些必须加载大量数据的单体。而那些被用作命名空间、特定页面专用代码包装器或组织相关适用方法的工具的单体最好还是立即实例化。
惰性加载单体的区别在于,对它们的访问必须借助于一个静态方法。
应该这样调用方法:Singleton.getInstance().methodName()
getInstance()方法会检查该单体是否已经被实例化。如果还没有,那么它将创建并返回其实例。如果单体已经实例化过,那么它将返回现有实例。
//惰性实例化单体 Namespace.DataParse = (function() { var instance = null; //单例对象 //把单例的所有代码放在这里封装 function constructor() { //private method function stripWhitespace(str) { return str.replace(/\s+/g, ''); } function stringSplit(str, delimiter) { return str.split(delimiter); } //特权方法 return { stringToArray: function(str, delimiter, stripWS) { if(stripWS) { str = stripWhitespace(str); } return stringSplit(str, delimiter); } } } return { getInstance: function() { if(!instance) { instance = constructor(); } return instance; } } })();
如果你需要创建一个延迟实例化的单体,那么最好为其编写一条注释解释这样做的原因,以免别人把它简化为普通单体。
单体模式适合的场所
从为代码提供命名空间和增强其模块性这个角度来说,你应该尽量使用单体模式。
在简单的项目中,你可以只是把单体用作命名空间,将自己的所有代码组织在一个全局变量名下。
在稍大的、稍复杂项目中,单体可以用来把相关代码组织在一起以便日后维护,或者把数据或代码安置在公开的单一位置。
在大型项目中,它可以起到优化作用,把那些开销大却又很少使用的组件包装到惰性加载单体中,而针对特定环境的代码可以包装在分支型单体中。
单体的好处
单体主要的好处在于它对代码的组织作用。
描述性的命名空间还可以增强代码自我说明性,有利于阅读和理解。
使用惰性实例化技术可以直接到需要一个对象时才创建它,从而减少那些不必要内存消耗。
分支技术则可以用来创建高效的方法(计算时间方面),创建为特定环境量身定制的方法,这种方法只会在第一次调用时去检查运行环境。
缺点
单体模式提供的只是一种单点访问,有可能会导致模块间的强耦合。有时创建一个可实例化的类更可取,哪怕它只会被实例化一次。
有时候某种更高的模式会更符合任务的需要。与惰性加载单例相比,虚拟代理能给予对类实例化方式更多的控制权。也可以用工厂模式来取代分支型单体。
下一章将具体讲解单体的用途