什么是享元模式?
享元模式是一种用于性能优化的模式,运用共享技术有效支持大量细粒度的对象。
嗯,这波是用官方的回答了废话,用人话来讲就是,中国为了让全民都可以自由读书,如果给每个人直接发一堆书显然不合理,那么怎么办呢?对的,就是开设新华书城,每个人都可以去免费读书,这样就做到了共享。
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
}
])
复制代码
看看结果
享元模式带来了性能优化,但是增加了系统的复杂度,要多维护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