js设计模式之享元模式

什么是享元模式?

享元模式是一种用于性能优化的模式,运用共享技术有效支持大量细粒度的对象。

嗯,这波是用官方的回答了废话,用人话来讲就是,中国为了让全民都可以自由读书,如果给每个人直接发一堆书显然不合理,那么怎么办呢?对的,就是开设新华书城,每个人都可以去免费读书,这样就做到了共享。

Talk is cheap,Show me the code.

class XinhuaBookCity {
  constructor() {
    this.bookCity = ['设计模式']
  }
  borrowBooks(title) {
    for (const book in this.bookCity) {
      if (this.bookCity[book] === title) {
        return this.bookCity[book]
      }
      this.purchase(title)
    }
  }
  purchase(title) {
    this.bookCity.push(title)
  }
}

const xinhuabookcity = new XinhuaBookCity()
console.log(xinhuabookcity.borrowBooks('设计模式'))
console.log(xinhuabookcity.borrowBooks('重构2'))
console.log(xinhuabookcity.borrowBooks('重构2'))

littlebird@ubuntu:~/享元模式$ node test.js 
设计模式
undefined
重构2
复制代码

再深入享元模式

上面书城的例子只是一个简单的雏形,但相信已经初步了解享元模式的思想了。

外部属性和内部属性

  • 内部属性存储于对象内部。
  • 内部属性可以被一些对象共享。
  • 内部属性独立于具体的场景,通常不会改变。
  • 外部属性取决于具体的场景,并根据场景而变化,外部属性不能被共享。

对于上面的例子,书城就是一个类部属性,内部属性可以被共享,通常是不会改变书城这个属性的,而被借的书是外部属性,这个是根据需要改变的,是不能被共享的。

上面享元例子的问题

  • 第一个是通过构造函数new显式创建了两个对象。

可以用一个对象工厂来解决这个问题,当某个共享对象被需要时才从工厂中创建出来。

  • 第二个是个共享对象手动添加外部状态。

可以用一个管理器来记录对象相关的外部状态,使这些外部状态通过某个钩子和共享对象联系起来。

下面用《javascript设计模式与开发实践》中享元模式里上传模块的例子

关于对象爆炸的问题,上传模块支持1000个文件同时上传,如果按照普通的开发思路的话,每选择一个文件就创建一个上传对象,这样就形成了对象爆炸的现象,造成了很大的性能消耗,这个时候就可以用享元模式来优化性能。

剥离外部属性

class Upload {
  constructor(uploadType) {
    this.uploadType = uploadType
  }
  delFile(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)
    }
  }
}
复制代码

在上传模块中,上传文件的类型是可以剥离出来当内部属性的,只要根据文件类型,就可以通过工厂函数构造对应的上传对象。

创建工厂函数

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

  return {
    create: (uploadType) => {
      if (createdFlyWeightObjs[uploadType]) {
        return createdFlyWeightObjs[uploadType]
      }
      return createdFlyWeightObjs[uploadType] = new Upload(uploadType)
    }
  }
})();
复制代码

如果上传对象已经被创建过了,就从共享对象中返回,如果没有的话,把上传对象放入共享对象中并返回。

外部属性的管理

let uploadManager = (function() {
  let uploadDatabase = {}

  return {
    add: (id, uploadType, fileName, fileSize) => {
      let flyWeightObj = UploadFactory.create(uploadType)

      let dom = document.createElement('div')
      dom.innerHTML =
              `<span>文件名称: ${fileName}, 文件大小: ${fileSize}</span>
              <button class='delFile'>删除<button/>`
      dom.querySelector('.delFile').onclick = () => {
        flyWeightObj.delFile(id)
      }
      document.body.appendChild(dom)

      uploadDatabase[id] = {
        fileName,
        fileSize,
        dom
      }

      return flyWeightObj
    },
    setExternalState: (id, flyWeightObj) => {
      let uploadDate = uploadDatabase[id]
      for (let i in uploadDate) {
        flyWeightObj[i] = uploadDate[i]
      }
    }
  }
})();
复制代码

测试

let id = 0

window.startUpload = function(uploadType, files) {
  for(let i = 0, file; file = file = files[i++];) {
    let 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: '4.html',
    fileSize: 3000
  },
  {
    fileName: '6.txt',
    fileSize: 5000
  }
])
复制代码

看看结果

upload.png

享元模式带来了性能优化,但是增加了系统的复杂度,要多维护factory和manager对象

享元模式的适用性

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

从享元模式得到的启发 —– 对象池技术

对象池维护一个装载空闲对象的池子,如果需要的时候,不是直接new,而是转从对象池里获取,如果对象池里没有空闲对象,则创建一个新的对象,http和数据库的连接池都是对象池的代表应用。

对象池的通用实现

let objectPoolFactory = function(createObjFn) {
  let objectPool = []

  return {
    create: () => {
      let obj = objectPool.length === 0 ?
          createObjFn.apply(this, arguments) : objectPool.shift()
      return obj
    },
    recover: (obj) => {
      objectPool.push(obj)
    }
  }
}

let iframeFactory = objectPoolFactory(function() {
  let iframe = document.createElement('iframe')
  document.body.appendChild(iframe)

  iframe.onload = function() {
    iframe.onload = null
    iframeFactory.recover(iframe)
  }
  return iframe
})

let iframe1= iframeFactory.create()
iframe1.src = 'http://baidu.com'

let iframe2 = iframeFactory.create()
iframe2.src = 'http://QQ.com'
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享