单例模式,也叫单子模式,属于创建型模式的一种。
在应用这个模式时,单例对象的类必须保证只有一个实例存在。许多时候整个系统只需要拥有一个的全局对象,这样有利于我们协调系统整体的行为。
比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息。这种方式简化了在复杂环境下的配置管理。
实现单例模式的思路
一个类能返回对象一个引用(永远是同一个)和一个获得该实例的方法(必须是静态方法,通常使用getInstance这个名称);
当我们调用这个方法时,如果类持有的引用不为空就返回这个引用,如果类保持的引用为空就创建该类的实例并将实例的引用赋予该类保持的引用;
同时我们还将该类的构造函数定义为私有方法,这样其他处的代码就无法通过调用该类的构造函数来实例化该类的对象,只有通过该类提供的静态方法来得到该类的唯一实例。
下面采用JavaScript实现单例模式:
var Singleton = function (name) {
this.name = name;
}
Singleton.instance = null;
Singleton.prototype.getName = function() {
alert(this.name);
}
Singleton.getInstance = function(name) {
if ( !this.instance ) {
this.instance = new Singleton(name);
}
return this.instance
}
var a = Singleton.getInstance('sven1');
var b = Singleton.getInstance('sven2');
alert( a === b ); // true
// 或者
var Singleton = function(name) {
this.name = name;
}
Singleton.prototype.getName = function() {
alert( this.name )
}
Singleton.getInstance = (function() {
var instance = null;
return function( name ) {
if ( !instance ) {
instance = new Singleton(name);
}
return instance;
}
})();
var a = Singleton.getInstance('sven1');
var b = Singleton.getInstance('sven2');
alert( a === b ); // true
可以应用在线程池、全局缓存、浏览器中的window对象、浮窗中。
Javascript单例模式的实践
全局变量符合这个单例模式,但是有必要尽量减少全局变量的使用,即使需要,也要把它的污染降到最低。
1.使用命名空间
var namespace1 = {
a: function() {
alert(1);
},
b: function() {
alert(2);
}
}
动态创建命名空间
var MyApp = {};
MyApp.namespace = function( name ) {
var parts = name.split('.');
var current = MyApp;
for( var i in parts) {
if ( !current[ parts[i] ] ) {
current[ parts[ i ]] = {}
}
current = current[ parts[ i ] ];
}
};
MyApp.namespace('event');
MyApp.namespace('dom.style');
console.dir( MyApp );
// 上述代码等价于:
var MyApp = {
event: {},
dom: {
style: {}
}
}
2.使用闭包封装私有变量
var user = (function() {
var __name = 'sven',
__age = 29;
return {
getUserInfo: function() {
return __name + '-' + __age
}
}
})
惰性单例
instance实例对象总是在我们调用Singleton.getInstance的时候才被创建,而不是在页面加载好的时候就创建,因此我们可以实现一个惰性单例。
Singleton.getInstance = (function() {
var instance = null;
return function( name ) {
if ( !instance ) {
instance = new Singleton( name );
}
return instance;
}
})
通用的惰性单例
var createIframe = (function() {
var iframe;
return function() {
if (!iframe) {
iframe = document.createElement('iframe');
iframe.style.display = 'none';
document.body.appendChild(iframe);
}
return iframe;
}
})
抽离
var getSingle = function(fn) {
var result;
return function() {
return result || (result = fn.apply(this, arguments));
}
}
例子
var createLoginLayer = function () {
var div = document.createElement('div');
div.innerHTML = '我是登录浮窗';
div.style.display = 'none';
document.body.appendChild(div);
}
var createSingleLoginLayer = getSingle(createLoginLayer);
document.getElementById('loginBtn').onClick = function() {
var loginLayer = createSingleLoginLayer();
loginLayer.style.display = 'block';
}
单例模式的优缺点
优点:
- 你可以保证一个类只有一个实例。
- 你获得了一个指向该实例的全局访问节点。
- 仅在首次请求单例对象时对其进行初始化。
缺点:
- 违反了单一职责原则。 该模式同时解决了两个问题。
- 单例模式可能掩盖不良设计, 比如程序各组件之间相互了解过多等。
- 单例的客户端代码单元测试可能会比较困难, 因为许多测试框架以基于继承的方式创建模拟对象。 由于单例类的构造函数是私有的, 而且绝大部分语言无法重写静态方法, 所以你需要想出仔细考虑模拟单例的方法。 要么干脆不编写测试代码, 或者不使用单例模式。
公众号@前端成长手册