享元模式
解决场景:
系统因为创建了大量类似的对象而导致内存占用过高。
享元模式的核心是运用共享技术来有效支持大量细粒度的对象。
资源共享 HTTP连接池 ,或者 对象连接池,数据库连接池都是用的这种设计模式。
享元模式是一种性能优化的模式,简称 (flyWeight) 。fly指苍蝇。
也就是蝇量级,很多苍蝇在你眼前乱飞,你烦不烦!
(相信我,等内存爆掉的时候,你会像图片上这个妹子一样。你会来网上找我这篇资料的。😏😏😏)
所以这个flyWeight相当形象,目的就是为了解决程序里太多的苍蝇。
例:
1. 前端应用1--服装展示。
(一) 场景假设:
有一个内衣工厂,目前需要50种男士和50种女士内衣。 正常需要50个男模特和50个女模特穿上进行展示。
var model = function(sex,underwear){
this.sex = sex;
this.underwear = underwear;
}
model.prototype.showTime = function(){ //展示
console.log(this.sex,this.underwear)
}
for(let i = 0;i<50;i++){
var male = new model('男','帅气');
male.showTime()
}
for(let i = 0;i<50;i++){
var female = new model('女','妩媚');
female.showTime()
}
上述例子创建了 50个男模特,50个女模特。很消耗内存。
(二) 改写:
var model = function(sex){
this.sex = sex;
}
model.prototype.showTime = function(){
console.log(this.underWear+"--"+this.sex)
}
var male = new model('男');
var female = new model('女');
for(let i = 0;i<50;i++){
male.underWear = '帅气' //额外属性可以随时变
male.showTime()
}
for(let j = 0;j<50;j++){
female.underWear = '妩媚' //额外属性可以随时变
female.showTime()
}
使用两个对象就完成了创建。
要点:
-
内部属性存储在对象内部。
-
内部属性可以被对象共享。
-
内部状态独立于具体场景,一般不变。
-
外部状态根据场景变化。 外部状态不能共享。
(三)上述例子存在两个问题,什么问题?
-
上例中一开始就创建了两个对象,分别为 male,female。实际场景中不一定刚开始就需要全部创建出来。解决方法,使用对象工厂。
-
外部属性(undenwear)和本身使用的对象(male,female)关联性很弱,我们需要用一个管理器记录相关状态,使的外部状态可以通过某个钩子和共享对象很好的关联起来。
2. 前端应用2--文件上传(对象爆炸问题)
(一)场景:
微云里的文件上传功能,需要同时支持2000个文件。每个文件都对应一个javaScript上传对象创建。也就是在程序里new 了2000个upload对象。 支持好几种方式,浏览器插件,flash上传和表单上传。
//用一个全局id作为每个文件唯一标识
var id = 0;
window.startUpload = function(uploadType,files){ //每次点击文件上传都会触发startUpload
for(let i = 0,file;file = files[i++];){
var uploadObj = new Upload(uploadType,file.fileName,file.fileSize)
uploadObj.init(id++);
}
}
//编写 upload 构造函数
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 = `<span>${this.fileName}</span> <button id="deleteId">删除</button>`
this.dom.querySelector('#deleteId').onclick = function(){
that.delFile();
}
document.body.appendChild(this.dom)
}
//额外编写删除方法
Upload.prototype.delFile = function(){
var res =window.confirm('是否删除?');
if(res){
this.dom.parentNode.removeChild(this.dom) //通过父级节点查找孩子节点,找到之后移除
}
}
//模拟触发点击上传
startUpload('flash',[
{fileName:"名字1",fileSize:20},
{fileName:"名字2",fileSize:30},
{fileName:"名字3",fileSize:30},
{fileName:"名字4",fileSize:30},
{fileName:"名字5",fileSize:30}
])
上述例子中创建传入5个file就创建了5个 uploadObj,显然很容易发生对象爆炸问题,接下来我们把它做优化:
(二) 优化
- 提炼公共属性,也就是大部分对象可以共享的属性,比如 利用flash下载。 uploadType。 下载类型.
var upload = function(uploadType){
this.uploadType = uploadType;
}
//同时都有删除方法
upload.prototype.delFile = function(id){
uploadManager.setExternalState(id,this)
//这个方法下文中会提到
//也就是把属性赋到当前对象上。
//对象由 { uploadType:"flash" } -> {uploadType:"flash",dom:...,fileName:...,fileSize:...}
var res =window.confirm('是否删除?');
if(res){
return this.dom.parentNode.removeChild(this.dom)
}
}
- 使用工厂函数方法创建对象,也就是用到的时候才创建,而且可以复用之前的对象:
var dactory = (function(){
var obj ={}
return {
create:function(uploadType){
if(obj[uploadType]){
return obj[uploadType]
}
return obj[uploadType] = new upload(uploadType)
}
}
})()
- 重点! 管理器封装外部属性。
var uploadManager = (function(){
var uploadDataBase = {};
//这里利用的是闭包 , 把所有属性进行汇总 ,等到需要用到的时候,
//再把他利用下文setExternalState方法传入 对象中。
//实际上就是把私有属性 都放入 uploadDataBase,
return {
add:function(id,uploadType,fileName,fileSize){
var flyWeightObj = dactory.create(uploadType);
var dom = document.createElement('div');
dom.innerHTML = `<span>文件夹</span> <button id="deleteId">删除</button>`
dom.querySelector('#deleteId').onclick = function(){
flyWeightObj.delFile(id)
}
document.body.appendChild(dom);
uploadDataBase[id] = {
uploadType,
fileName,
fileSize,
dom
}
//这里 add只是主要做一个添加 dom操作
return uploadDataBase
},
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){ //files所有文件 uploadType要传的flash文件类型
for(var i = 0,file;file = files[i++];){
uploadManager.add(id++,uploadType,file.fileName,file.fileSize)
}
}
startUpload('flash',[{ //模拟执行上传
fileName:"1.txt",
fileSize:"20302"
},
{
fileName:"2.txt",
fileSize:"123"
},
{
fileName:"3.txt",
fileSize:"456"
},
{
fileName:"4.txt",
fileSize:"875"
}
,{
fileName:"5.txt",
fileSize:"1234"
}])
(四). 结果
(五). 文件上传例子总结:
上一个例子用白话说就是,
(1)首先,把大部分对象里的公共属性抽离 ,同时能利用少部分对象进行归类,
比如:男 女中把性别 sex当作对象,这样就能单创建2个对象。
比如:鞋子,运动鞋一个对象,跑步鞋一个对象。
又或者上述文件上传,上传文件的类型一个对象 (flash上传),旨在减少对象创建的次数。
(2)其次,利用对象工厂,当你想创建一个对象的时候,在工厂里找之前有没有创建过对象,
创建过,就继续用之前的那个对象,节约空间。
(3) 利用闭包机制,让函数有记忆,把所有对象里的属性都用一个数组存起来(uploadDataBase),等到对象真正
需要用的时候,再把数组里对应的属性找出来赋给当前对象(用id进行对应,看 delFile方法)。
也就是 没有使用这种模式之前
...
创建的对象 :
{id:1,fileName:"1.txt",uploadType:"flash",fileSize:... }
{id:2,fileName:"2.txt",uploadType:"flash",fileSize:... }
{id:3,fileName:"3.txt",uploadType:"flash",fileSize:... }
{id:4,fileName:"4.txt",uploadType:"flash",fileSize:... }
{id:5,fileName:"5.txt",uploadType:"flash" ,fileSize:...}
{id:6,fileName:"6.txt",uploadType:"flash",fileSize:... }
{id:7,fileName:"7.txt",uploadType:"flash" ,fileSize:...}
...
使用之后
```
一个对象 {uploadType:"flash"} // 对象flyWeightObj 重头到尾只创建了一个对象。
//再创建一个对象 //对象 uploadDataBase
用来收集其他的 fileName,id,uploadType,fileSize:
{
id:{
fileName:...,
fileSize:...,
//等等其他属性
}
}
最后,等到要使用这个对象(flyWeightObj)的其他属性的时候,再把 UploadDataBase 中的属性拿出来赋给
flyWeightObj。
```
享元模式理解简单,但是要真写出来还是太不容易。各位看官老爷自行体会。
总结
场景: 用了大量相似对象,内存爆了。可以用几个共享对象取代大量对象的时候,应该使用这种模式。
最后,看官老爷请注意,文中有任何错误请指正,不甚感谢。欢迎交流。
javaScript 设计模式与开发实践 --曾探