Web前端开发JavaScript设计模式 -- 单体模式(The Singleton Pattern)

前言
在Web前端项目的开发中,争取做到编写代码的结构清晰、易读、易维护、安全等方面的重要性是不言而喻的。为了更好地进行代码编写,借鉴设计模式中的相关编程思想是比较有帮助的,下面会陆续介绍一些简单易用的编程设计模式,希望对大家在项目开发中有所帮助。介绍中的代码示例主要由JavaScript代码编写,所阐述的设计思想在其它编程语言大多是通用的。

参考

1. Pro javascript design patterns/javascript设计模式 (Ross Harmes and Dustin Diaz)
2. Design Patterns: Elements of Reusable Object-Oriented Software /设计模式 可复用面向对象软件的基础( [美] Erich Gamma / Richard Helm / Ralph Johnson / John Vlissides)

正文
单体模式(The Singleton Pattern)

单体模式是JavaScript最基本常用的模式之一。它提供了一种将代码组织为一个逻辑单元(对象)然后通过单一的变量(对象名)来访问的方式。

1. 单体模式的主要用途:

(1). 为代码划分命名空间,减少全局变量的数量,提高代码安全性;
(2). 代码延迟加载(lazy loading);
(3). 代码选择性调用(branching);
(4). ...

2. 单体模式基本结构
2.1 结构
/* Basic Singleton. */
var Singleton = {
	attribute1: true,
	attribute2: 10,
	method1: function() {
	},
	method2: function(arg) {
	}
	// Other methods can go here as well.
};


2.2 访问方法

Singleton.attribute1 = false;
var total = Singleton.attribute2 + 5;
var result = Singleton.method1();

3. 划分命名空间
var MyNamespace = {
	findProduct: function(id) {
	...
},
// Other methods can go here as well.
}

命名空间可以减少全局变量的数量,提高代码安全性。

4. 拥有私有成员的单体

4.1 私有成员下划线表示方法
/* DataParser singleton, converts character delimited strings into arrays. */
GiantCorp.DataParser = {
	// Private methods.
	_stripWhitespace: function(str) {
		return str.replace(/\s+/, '');
	},
	_stringSplit: function(str, delimiter) {
		return str.split(delimiter);
	},
	// Public method.
	stringToArray: function(str, delimiter, stripWS) {
		if(stripWS) {
		str = this._stripWhitespace(str);
	}
	var outputArray = this._stringSplit(str, delimiter);
		return outputArray;
	}
};

下划线是在单体对象内表示私有成员比较简单的方法,以表示该成员是私有的只在对象内部使用。在上面的示例中关键字this换为GiantCorp.DataParser在大多数情况下更为保险一些。
下划线私有成员表示法结构简单清晰,但私有成员不是真正的为此对象私有,外部依然可以访问到(GiantCorp.DataParser._stripWhitespace(str);)。

4.2 私有成员使用闭包封装
4.2.1 闭包的简单结构
MyNamespace.Singleton = function() {
	return {};
}();
//or
MyNamespace.Singleton = (function() {
	return {};
})();

上述的两个代码示例所创建的MyNamespace.Singleton是相同的,其中的匿名函数返回一个对象赋给MyNamespace.Singleton。其定义后的一对括号使得这个匿名函数立即执行。

4.2.2 公用成员添加到return{}中,放回给单体:
MyNamespace.Singleton = (function() {
	return { // Public members.
		publicAttribute1: true,
		publicAttribute2: 10,
		publicMethod1: function() {
			...
		},		
		publicMethod2: function(args) {
			...
		}
	};
})();

4.2.3 私有成员添加:
MyNamespace.Singleton = (function() {
	// Private members.
	var privateAttribute1 = false;
	var privateAttribute2 = [1, 2, 3];
	function privateMethod1() {
		...
	}
	function privateMethod2(args) {
		...
	}
	return { // Public members.
		publicAttribute1: true,
		publicAttribute2: 10,
		publicMethod1: function() {
			...
		},
		publicMethod2: function(args) {
			...
		}
	};
})();

私有成员添加到匿名函数中(在return{}外),这里添加的私有成员是真正的私有成员,只能在匿名函数和return返回的对象中访问。
这种单体模式也称为模块模式(module pattern),指的是它可以把一批相关方法和属性组织为模块,同时也起到了划分命名空间的作用。
私有成员使用闭包封装的优点:私有成员放到闭包中可确保其不会在单体之外被引用,改变对象的实现细节不会影响到别人的代码。

4.2.4 私有成员使用闭包封装时单体的公共成员和私有成员的声明语法不同:
(1). 公共成员声明在放回对象内部,私有在外部;
(2). 私有属性必须用var声明,否则它将成为全局性的;
(3). 私有方法按function funName(args){...}方式声明,在最后一个大括号之后不需要使用分号;
(4). 公共属性和方法按attributeName: attributeValue,和methodName: function(args) { ... },方式声明,成员之间由逗号隔开;

5. 延迟实例化(Lazy Instantiation)
这种方法适用于资源密集型或配置开销较大的单体,在需要使用时再将其实例化(lazy loading)。

5.1 延迟实例化代码构造方法
以4.2.3 代码为例,构造前代码:
MyNamespace.Singleton = (function() {
	// Private members.
	var privateAttribute1 = false;
	var privateAttribute2 = [1, 2, 3];
	function privateMethod1() {
		...
	}
	function privateMethod2(args) {
		...
	}
	return { // Public members.
		publicAttribute1: true,
		publicAttribute2: 10,
		publicMethod1: function() {
			...
		},
		publicMethod2: function(args) {
			...
		}
	};
})();

步骤1:把所有代码放到一个名为constructor的方法中
/* General skeleton for a lazy loading singleton, step 1. */
MyNamespace.Singleton = (function() {
	function constructor() { // All of the normal singleton code goes here.
		// Private members.
		var privateAttribute1 = false;
		var privateAttribute2 = [1, 2, 3];
		function privateMethod1() {
			...
		}
		function privateMethod2(args) {
			...
		}
		return { // Public members.
			publicAttribute1: true,
			publicAttribute2: 10,
			publicMethod1: function() {
				...
			},
			publicMethod2: function(args) {
				...
			}
		}
	}
})();

步骤2:返回一个getInstance方法,用于控制constructor()的加载
/* General skeleton for a lazy loading singleton, step 2. */
MyNamespace.Singleton = (function() {	
	function constructor() { // All of the normal singleton code goes here.
		...
	}
	return {
		getInstance: function() {
		// Control code goes here.
		}
	}
})();

步骤3:getInstance控制实例化
/* General skeleton for a lazy loading singleton, step 3. */
MyNamespace.Singleton = (function() {
	var uniqueInstance; // Private attribute that holds the single instance.
	function constructor() { // All of the normal singleton code goes here.
		...
	}
	return {
		getInstance: function() {
			if(!uniqueInstance) { // Instantiate only if the instance doesn't exist.
				uniqueInstance = constructor();
			}
			return uniqueInstance;
		}
	}
})();

5.2 延迟加载单体调用方法
MyNamespace.Singleton.getInstance().publicMethod1();

6. 分支(branching)
主要用于根据不同情况为单体返回不同的对象。

6.1 分支简单结构
/* Branching Singleton (skeleton). */
MyNamespace.Singleton = (function() {
	var objectA = {
		method1: function() {
			...
		},
		method2: function() {
			...
		}
	};
	var objectB = {
		method1: function() {
			...
		},
		method2: function() {
			...
		}
	};
	return (someCondition) ? objectA : objectB;
})();

分支技术存在代码冗余,在使用时要考虑时间和内存的权衡。

7. 单体模式小结

单体模式是最常用的设计模式之一,特别是在JavaScript编程中,其优点在前文已见简单介绍过来,不足之处在于有时可能会导致模块间的强耦合,不利于单元测试。在实际应用中单体模式可以和其它设计模式结合使用。不同的设计模式有自己的优点,在不同的具体环境和需求中可以选择最合适的。



你可能感兴趣的:(JavaScript)