美文网首页前端知识总结js设计模式
js常用设计模式7-享元模式

js常用设计模式7-享元模式

作者: 青色琉璃 | 来源:发表于2021-01-18 11:44 被阅读0次

js常用设计模式1-单例模式
js常用设计模式2-策略模式
js常用设计模式3-代理模式
js常用设计模式4-发布-订阅模式
js常用设计模式5-命令模式
js常用设计模式6-组合模式
js常用设计模式7-享元模式
js常用设计模式8-职责链模式
js常用设计模式9-中介者模式
js常用设计模式10-装饰者模式
js常用设计模式11-状态模式

享元模式是一种用于性能优化的模式,核心是运用共享技术来有效支持大量细粒度的对象。
如果系统中创建了大量类似的对象,导致内存占用过高,这时候享元模式就很有用了。

1,假发工厂

假设有一个假发工厂,生产50种男士假发和50种女士假发,这时候我们需要50个男模和50个女模,分别套上假发,然后拍照:

var Model = function (sex, underware) {
  this.sex = sex
  this.underware = underware
}

Model.prototype.takePhoto = function () {
  console.log('sex:' + this.sex + 'underware:' + this.underware)
}

for (var i = 0; i < 50; i++) {
  var maleModel = new Model('男', 'underware' + i)
  maleModel.takePhoto()
}
for (var i = 0; i < 50; i++) {
  var femaleModel = new Model('女', 'underware' + i)
  femaleModel.takePhoto()
}

很明显,这种做法略显沙雕。

2,享元模式优化

实际上,我们只需要两个模特就够了,假发换着套就行。

//效果一样,感觉好多了
var Model = function (sex) {
  this.sex = sex
}
Model.prototype.takePhoto = function () {
  console.log('sex:' + this.sex + 'underware:' + this.underware)
}

var maleModel = new Model('男')
var femaleModel = new Model('女')

for (var i = 0; i < 50; i++) {
  maleModel.underware = 'underware' + i
  maleModel.takePhoto()
}

for (var i = 0; i < 50; i++) {
  femaleModel.underware = 'underware' + i
  femaleModel.takePhoto()
}

3,内部状态与外部状态

享元模式的目标是尽量减少共享对象的数量:
(1)内部状态存储于对象内部
(2)内部状态可以被一些对象共享
(3)内部状态独立于具体的场景
(4)外部状态取决于具体的场景,并根据场景而变化,外部状态不能被共享
享元模式是一种时间换空间的优化模式

4,普通版本上传文件的例子

现在我们要实现一个功能:往服务器上传文件。假设目前只有两种上传方式:插件上传和flash上传。这俩实现是一样的,选择文件之后,都会调用window下的startUpload方法,用户选择的文件列表被组合成一个数组files放到该函数的参数列表中。
代码如下:

  var id = 0;
  //uploadType区分是插件上传还是flash
    window.startUpload = function (uploadType, files) {
      for (var i = 0, file; file = files[i++];) {
        var upload = new Upload(uploadType, file.fileName, file.fileSize)
        upload.init(id++)
      }
    }

用户选择完文件后,startUpload函数会遍历files数组,挨个创建对应的upload对象。现在我们要定义Upload构造函数,除了初始化init函数之外,我们还需要一个删除函数delFile。代码如下:

// uploadType:上传类型, fileName:文件名, fileSize:文件大小
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},文件大小:${this.fileSize}</span><button class="delFile">删除</button>`
  this.dom.querySelector('.delFile').onclick = function () {
    that.delFile()
  }
  document.body.appendChild(this.dom)
}
Upload.prototype.delFile = function () {
  if (this.fileSize < 3000) {
    return this.dom.parentNode.removeChild(this.dom)
  }
  if (window.confirm('确定删除该文件吗?' + this.fileName)) {
    return this.dom.parentNode.removeChild(this.dom)
  }
}

现在,让我们来try一try:

startUpload('plugin', [
  {
    fileName: '1.txt',
    fileSize: 1000
  },
  {
    fileName: '2.txt',
    fileSize: 3000
  },
  {
    fileName: '3.txt',
    fileSize: 5000
  }
])

startUpload('flash', [
  {
    fileName: '4.txt',
    fileSize: 1000
  },
  {
    fileName: '5.txt',
    fileSize: 3000
  },
  {
    fileName: '6.txt',
    fileSize: 5000
  }
])

5,享元模式优化文件上传

上面这个上传功能,有多少个需要上传的对象,就创建了多少个upload对象,非常的浪费。下面,我们用享元模式来重构它。
(1)首先,要明确内部状态
upload对象依赖于upLoadType属性,由于我们现在是简化版的上传,因此只要明确了upLoadType,这个upload对象是可以被其他文件共享的。而fileName、fileSize都不一样,因此这俩是外部状态。
(2)剥离外部状态
现在,我们的Upload构造函数中,只需要upLoadType这一个参数就可以:

 var Upload = function (uploadType) {
      this.uploadType = uploadType
    }

Upload.prototype.init函数也不需要了,现在upload对象的初始化被放在了uploadManager.add这个函数中。
接下来我们只需要定义Upload.prototype.delFile函数就ok:

  Upload.prototype.delFile = function (id) {
      // 删除之前,读取文件实际大小
      uploadManager.setExternalState(id, this)

      if (this.fileSize < 3000) {
        return this.dom.parentNode.removeChild(this.dom)
      }
      if (window.confirm('确定删除该文件吗?' + this.fileName)) {
        return this.dom.parentNode.removeChild(this.dom)
      }
    }

(3)工厂函数进行对象实例化
定义一个工厂,用来创建upload对象,如果某种内部状态(upLoadType)的对象已经被创建过,那么直接返回这个对象,否则创建新的对象:

   var UploadFactory = (function () {
      var createdFlyWeightObjs = {}

      return {
        create: function (uploadType) {
          if (createdFlyWeightObjs[uploadType]) {
            return createdFlyWeightObjs[uploadType]
          }
          return createdFlyWeightObjs[uploadType] = new Upload(uploadType)
        }
      }
    })()

(4)管理器封装外部状态
在(2)中我们提到了uploadManager对象,这个对象负责向UploadFactory提交创建对象的请求,并用一个uploadDatabase对象保存所有的upload对象的外部状态,以便在程序运行中给upload共享对象设置外部状态:

  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]
          }
        }
      }
    })()

现在,我们要触发startUpload 操作了:

   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)
      }
    }

让我们try一try最后的效果:

  startUpload('plugin', [
      {
        fileName: '1.txt',
        fileSize: 1000
      },
      {
        fileName: '2.txt',
        fileSize: 3000
      },
      {
        fileName: '3.txt',
        fileSize: 5000
      }
    ])

    startUpload('flash', [
      {
        fileName: '4.txt',
        fileSize: 1000
      },
      {
        fileName: '5.txt',
        fileSize: 3000
      },
      {
        fileName: '6.txt',
        fileSize: 5000
      }
    ])

6,小结
享元模式是一个很好的性能优化方案,但是因为多了uploadManager和UploadFactory 对象,也会带来维护问题。
享元模式适用场景:

  • 一个程序中大量使用相似对象
  • 对于使用大量对象,造成了很大的内存开销
  • 对象的大多数状态可以变为外部状态
  • 剥离对象的外部状态后,可以用较少的共享对象取代大量对象

相关文章

  • js常用设计模式7-享元模式

    js常用设计模式1-单例模式[https://www.jianshu.com/p/5cc2ca56a17b]js常...

  • 面试 (七) : 其他篇 -- 设计模式

    常用的设计模式 • 单例模式 • 组合模式 • 观察者模式 • 代理模式 • 享元模式 • 工厂方法模式 • 抽象...

  • 结构型模式7-享元模式

    结构型模式7-享元模式 享元模式Flyweight 意图 运用共享技术有效地支持大量细粒度的对象。 问题思考 Wo...

  • iOS面试

    常用的设计模式 •单例模式 •组合模式 •观察者模式 •代理模式 •享元模式 •工厂方法模式 •抽象工厂模式 #M...

  • 好程序员Java培训​分享java设计模式之享元模式

    好程序员Java培训​分享java设计模式之享元模式,Java设计模式中的享元模式。享元模式有点类似于单例...

  • 6. iOS面试题其他篇1

    常用的设计模式 单例模式 组合模式 观察者模式 代理模式 享元模式 工厂方法模式 抽象工厂模式 MVC的理解 数据...

  • iOS面试题13-其他

    《2018 iOS面试题系列》 1.常用的设计模式 单例模式组合模式观察者模式代理模式享元模式工厂方法模式抽象工厂...

  • Java设计模式——享元模式

    Java设计模式之享元模式 这期跟大家聊的的设计模式是享元模式,该模式也是结构模式。 简介 利用共享的方式解决大量...

  • Java设计模式_享元模式

    点击链接跳转:设计模式——享元模式

  • 二十一、享元模式

    1. 何为享元模式 定义:通过共享已存在的对象,减少创建对象内存开销的设计模式被称为享元模式。 享元模式和单例模式...

网友评论

    本文标题:js常用设计模式7-享元模式

    本文链接:https://www.haomeiwen.com/subject/epaoaktx.html