本文由我们团队肖建朋总结
最近开发由于项目越来越大,发现项目中需要优化的地方越来越多,就抽空看看了享元模式。
享元模式(flyweight)是一种用于性能优化的模式,“fly”在这里指苍蝇的意思,意为蝇量级。享元模式的核心是运用共享技术来有效支持大量细粒度的对象。
初识享元模式
看概念可能会有些懵逼,那么我看先来看个例子:有个服装厂,生产了男女服装各50种款式,为了推销需要找模特来拍照,正常可能会找男女模特各50个,每人穿一种拍一组照片。这时用程序实现就会这样写:
var Model = function(sex, clothes) {
this.sex = sex
this.clothes = clothes
this.takePhoto = function() {
console.log( 'sex= ' + this.sex + ' clothes=' + this.clothes);
}
}
let iM = 1
while(iM <= 50){
var model = new Model('男', 'clothes' + iM)
model.takePhoto()
iM++
}
let iF = 1
while(iF <= 50){
var model = new Model('女', 'clothes' + iF)
model.takePhoto()
iF++
}
以上代码可以看出需要创建100个model对象,随着衣服种类的增多,就需要创建更多的对象,当对象多到一定数量程序必然会崩溃。
下面我们就用享元模式来优化一下这段代码,首先我们可以发现一个男模特是可以穿50种男式衣服的任意一件的。同样女模特可以穿50种女式衣服任意一件。所以对于服装厂来说只需要找一个男模特和一个女模特就可以了。
改造后的代码如下:
var Model = function(sex) {
this.sex = sex
this.takePhoto = function() {
console.log( 'sex= ' + this.sex + ' clothes=' + this.clothes);
}
}
var mModel = new Model('男')
var fModel = new Model('女')
var i = 1
while(i <= 50) {
mModel.clothes = ' clothes=男式衣服=' + i
mModel.takePhoto()
fModel.clothes = ' clothes=女式衣服=' + i
fModel.takePhoto()
i++
}
这里我们只创建一个男模特对象和一个女模特对象,然后让他们一次试穿拍照男女的50套衣服,代码是不是清爽了许多,而且空间复杂对也降低了。
享元模式的内部状态和外部状态
享元模式要求将对象的属性划分为内部状态与外部状态(这里的状态通常指属性)。享元模式的目标是尽量减少共享对象的数量。
划分内部状态和外部状态的可以从以下几点来区分:
- 内部状态存储于对象的内部。
- 内部状态可以被一些对象共享。
- 内部状态独立于具体的场景,通常不会改变。
- 外部状态取决于具体的场景,并根据场景而变化,外部状态不能被共享。
根据上面几点我们可以看出上一个例子中,sex
就是Model
的内部状态,clothes
就是它的外部状态
由于组装外部状态成为一个完整对象的过程需要花费一定的时间,但是可以大大减少系统中的对象数量,因此享元模式是一种用时间换空间的优化模式。
享元模式的通用结构
上面衣服和模特的例子,初步展示了享元模式的威力,但这并不是一个完整的享元模式,因为存在以下问题:
- 模特对象在其他场景中可能并不需要一开始就创建所有共享对象。
- 外部状态在其他场景中可能会非常复杂,这样他们与共享对象的联系就会变得很困难。
解决了这两个问题,就可以得出享元模式的通用结构:
- 工厂模式创建共享对象,当共享对象被真正需要时,它才从工厂中被创建出来。
- 管理器记录外部状态,使用一个管理器来记录对象相关的外部状态,使这些外部状态通过某个钩子和共享对象联系起来。
基于上面总结的享元模式通用结构,我们来实现一个完整的享元模式例子:文件上传
文件上传
// 构造Upload 对象,包含内部状态 uploadType 和一个delFile方法
var Upload = function( uploadType){
this.uploadType = uploadType;
this.delFile = function(id) {
uploadManager.setExternalState( id, this ); // (1)
if ( this.fileSize < 3000 ){
return this.dom.parentNode.removeChild( this.dom );
}
if ( window.confirm( '确定要删除该文件吗? ' + this.fileName ) ){
return this.dom.parentNode.removeChild( this.dom );
}
}
}
// 构造上传工厂来创建upload对象
var UploadFactory = (function () {
var createdFlyWeightObjs = {};
return {
create: function (uploadType) {
if (createdFlyWeightObjs[uploadType]) {
return createdFlyWeightObjs[uploadType];
}
return createdFlyWeightObjs[uploadType] = new Upload(uploadType);
}
}
})();
// 上传管理器,负责向UploadFactory提交对象创建申请,并保存所有upload对象的外部状态到uploadDatabase对象中,
var uploadManager = (function(){
var uploadDatabase = {};
return {
add: function (id, uploadType, fileName, fileSize) {
var flyWeightObj = UploadFactory.create(uploadType);
var dom = document.createElement('div');
dom.innerHTML =
'<span>文件名称:' + fileName + ', 文件大小: ' + fileSize + '</span>' +
'<button class="delFile">删除</button>';
dom.querySelector('.delFile').onclick = function () {
flyWeightObj.delFile(id);
}
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;
// startUpload上传函数,调用uploadManager
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
},
{
fileName: '2.html',
fileSize: 3000
},
{
fileName: '3.txt',
fileSize: 5000
}
]);
startUpload('flash', [
{
fileName: '4.txt',
fileSize: 1000
},
{
fileName: '5.html',
fileSize: 3000
},
{
fileName: '6.txt',
fileSize: 5000
}
]);
享元模式的使用场景
- 一个程序中使用了大量的相似对象。
- 由于使用了大量对象,造成内存开销过大。
- 对象的大多数状态都可以变为外部状态。
- 剥离出对象的外部状态后,可以用较少的共享对象取代大量对象。
总结
享元模式是一种为了解决性能问题而生的模式,在一个存在大量相似对象的系统中,享元模式可以很好的解决大量对象带来的性能问题。