[TOC]
# 享元模式
享元(flyweight)模式是一种用于性能优化的模式,“fly”在这里是苍蝇的意思,意为蝇量级。享元模式的核心是运用共享技术来有效支持大量细粒度的对象。
享元模式是为解决性能而生的模式,在一个存在大量相似对象的系统中,享元模式可以很好地解决大量对象带来的性能问题。
## 内部状态与外部状态
享元模式要求将对象的属性划分为内部状态与外部状态(状态在这里通常指属性)。
- 内部状态存储于对象内部。
- 内部状态可以被一些对象共享。
- 内部状态独立于具体的场景,通常不会改变。
- 外部状态取决于具体的场景,并根据场景而变化,外部状态不能被共享。
这样一来便可以把所有内部状态相同的对象都指定为同一个共享的对象。而外部状态可以从对象身上剥离出来,并储存在外部。
剥离了外部状态的对象成为共享对象,外部状态在必要时被传入共享对象来组装成一个完整的对象。
可以被对象共享的属性通常被划分为内部状态,如同不管什么样式的衣服,都可以按照性别不同,穿在同一个男模或女模身上,模特的性别就可以作为内部状态存储在共享对象的内部。而外部状态取决于具体的场景,并根据场景而变化,它们不能被一些对象共享,因此只能划分为外部状态。
## 享元模式的通用结构
- 创建工厂,只有当某种共享对象被真正需要时,它才从工厂中被创建出来。
- 管理器来记录对象相关的外部状态,使这些外部状态通过某个钩子和共享对象联系起来。
## 文件上传的例子
改造前:
```javascript
var Upload = function(uploadType, fileName, fileSize){
this.uploadType = uploadType;
this.fileName = fileName;
this.fileSize = fileSize;
this.dom = null;
};
Upload.prototype.init = function(id){
var that = this;
this.id = id;
this.dom = document.createElement('div');
this.dom.innerHTML = '...';
document.body.appendChild(this.dom);
};
var id = 0;
window.startUpload = function(uploadType, files){
for(var i = 0, file; file = files[i++];){
var uploadObj = new Upload(uploadType, file.fileName, file.fileSize);
uploadObj.init(id ++);
}
};
startUpload('plugin', [
{
fileName: '1.txt',
fileSize: 1000
},
...
]);
```
上面的代码中有多少个需要上传的文件,就一共创建了多少个`upload`对象。
重构:
```javascript
// uploadType作为内部状态,再抽离外部状态
var Upload = function(uploadType){
this.uploadType = uploadType;
};
// 定义删除文件的方法
Upload.prototype.delFile = function(id){
uploadManager.setExternalState(id, this); // 设置外部状态
if(this.fileSize < 3000){
return this.dom.parentNode.removeChild(this.dom);
}
if(window.confirm('确定要删除文件吗?'+ file.fileName)){
return this.dom.parentNode.removeChild(this.dom);
}
};
// 工厂进行对象实例化
var UploadFactory = (function(){
var createFlyWeightObjs = {};
return {
create: function(uploadType){
if(createFlyWeightObjs[uploadType]){
return createFlyWeightObjs[uploadType];
}
return createFlyWeightObjs[uploadType] = new Upload(uploadType);
}
}
})();
// 管理器封装外部状态
var uploadManager = (function(){
var uploadDataBase = {}; // 存储外部状态
return {
add: function(id, uploadType, fileName, fileSize){
var flyWeightObj = UploadFactory.create(uploadType);
var dom = document.createElement('div');
dom.innerHTML = '...';
document.body.appendChild(dom);
uploadDataBase[id] = { // 添加外部状态
fileName: fileName,
fileSize: fileSize,
dom: dom
};
return flyWeightObj;
},
setExternalState: function(id, flyWeightObj){ // 设置外部状态
var uploadData = uploadDataBase[id];
for(var i in uploadData){
flyWeightObj[i] = uploadData[i];
}
}
}
})();
var id = 0;
window.startUpload = function(uploadType, files){
for(var i = 0, file; file = files[i++];){
var uploadObj = uploadManager.add(++id, uploadType, file.fileName, file.fileSize);
}
};
startUpload('plugin', [
{
fileName: '1.txt',
fileSize: 1000
},
...
]);
```
## 享元模式的适用性
- 一个程序中使用了大量的相似对象。
- 由于使用了大量对象,造成很大的内存开销
- 对象的大多数状态都可以变为外部状态
- 剥离出对象的外部状态之后,可以用相对较少的共享对象取代大量对象。
## 再谈谈内部状态和外部状态
实现享元模式的关键是把内部状态和外部状态分离开来。有多少种内部状态的组合,系统中便最多存在多少个共享对象,而外部状态储存在共享对象的外部,在必要时被传入共享对象来组装成一个完整的对象。
## 对象池
对象池维护一个装载空闲对象的池子,如果需要对象的时候,不是直接`new`,而是转从对象池里获取。如果对象池没有空闲对象,则创建一个新的对象,当获取出的对象完成它的职责之后,再进入池子等待被下次获取。
### 通用对象池
```javascript
var objectPoolFactory = function(createObjFn){
var objectPool = [];
return {
create: function(){
var obj = objectPool.length === 0 ?
createObjFn.apply(this, arguments) : objectPool.shift();
return obj;
},
recover: function(obj){
objectPool.push(obj);
}
};
};
// 现在利用`ObjectPoolFactory`来创建一个装载一些`iframe`的对象池
var iframeFactory = objectPoolFactory(function(){
var iframe = document.createElement('iframe');
document.body.appendChild(iframe);
iframe.onload = function(){
iframe.onload = null;
iframeFactory.recover(iframe);
}
return iframe;
});
var iframe1 = iframeFactory.create();
iframe1.src = 'http://baidu.com';
var iframe2 = iframeFactory.create();
iframe2.src = 'http://qq.com';
setTimeout(function(){
var iframe3 = iframeFactory.create();
iframe3.src = 'http://163.com';
}, 3000);
```
对象池是另外一种性能优化方案,它跟享元模式有一些相似之处,但没有分离内部状态和外部状态这个过程。