(前端)每天一种设计模式-享元模式

420 阅读4分钟

享元模式

解决场景:

  系统因为创建了大量类似的对象而导致内存占用过高。

享元模式的核心是运用共享技术来有效支持大量细粒度的对象。

  资源共享 HTTP连接池 ,或者 对象连接池,数据库连接池都是用的这种设计模式。

享元模式是一种性能优化的模式,简称 (flyWeight) 。fly指苍蝇。

  也就是蝇量级,很多苍蝇在你眼前乱飞,你烦不烦!

202112230000210105 (1).gif

  (相信我,等内存爆掉的时候,你会像图片上这个妹子一样。你会来网上找我这篇资料的。😏😏😏)

  所以这个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()
    }

使用两个对象就完成了创建。

要点:

  1. 内部属性存储在对象内部。

  2. 内部属性可以被对象共享。

  3. 内部状态独立于具体场景,一般不变。

  4. 外部状态根据场景变化。 外部状态不能共享。

(三)上述例子存在两个问题,什么问题?

  1. 上例中一开始就创建了两个对象,分别为 male,female。实际场景中不一定刚开始就需要全部创建出来。解决方法,使用对象工厂。

  2. 外部属性(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}
  ])

屏幕截图 2022-03-26 084114.png

上述例子中创建传入5个file就创建了5个 uploadObj,显然很容易发生对象爆炸问题,接下来我们把它做优化:

(二) 优化

  1. 提炼公共属性,也就是大部分对象可以共享的属性,比如 利用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)  
        }
    }
  1. 使用工厂函数方法创建对象,也就是用到的时候才创建,而且可以复用之前的对象:
var dactory = (function(){
  var obj ={}  
    return {
        create:function(uploadType){
            if(obj[uploadType]){
              return obj[uploadType]
            }
           return obj[uploadType] = new upload(uploadType)  
        }
   }  
})()
  1. 重点! 管理器封装外部属性。
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"  
    }])

(四). 结果

屏幕截图 2022-03-26 084114.png

(五). 文件上传例子总结:

上一个例子用白话说就是, 

(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 设计模式与开发实践 --曾探