JavaScript设计模式-享元模式

概念

  1. 享元(flyweight)模式是一种用于性能优化的模式,“fly”在这里是苍蝇的意思,意为蝇量
    级。享元模式的核心是运用共享技术来有效支持大量细粒度的对象。

  2. 如果系统中因为创建了大量类似的对象而导致内存占用过高,享元模式就非常有用了。在
    JavaScript 中,浏览器特别是移动端的浏览器分配的内存并不算多,如何节省内存就成了一件非
    常有意义的事情。

例子

// 有一家模特儿公司(工厂)
        const modelFactory = function (type, name) {
            this.type = type
            this.name = name
        }
        // 给穿上指定衣服的模特儿拍照,
        modelFactory.prototype.takePhoto = function () {
            console.log('给type= ' + this.type + ' name=' + this.name + '拍照')
        }
        // 有50件不同的西装,需要拍50张照片
        for (let i = 1; i <= 50; i++) {
            // 给每件西装都请一位模特儿,一共请了50个
            const suitModel = new modelFactory('西装', i)
            suitModel.takePhoto()
        }
        // 有50件不同的羽绒服,需要50张照片
        for (let j = 1; j <= 50; j++) {
            // 给每件羽绒服都请一位模特儿,一共请了50个
            const jacketModel = new modelFactory('羽绒服', j)
            jacketModel.takePhoto()
        }

        // 上面new了100个对象,这样是很占内存(资源),我们可以用享元模式优化
        //我们只需要两位模特就行了,一个西装模特,一个羽绒服模特
         const modelFactory = function (type) {
             this.type = type
         }
         modelFactory.prototype.takePhoto = function () {
             console.log('给type= ' + this.type + ' name=' + this.name + '拍照')
         }
         // 分别创建一个西装模特对象和一个羽绒服模特对象:
        const suitModel = new modelFactory('西装')
        const jacketModel = new modelFactory('羽绒服')
        // 给模特依次穿上西装,并进行拍照:
        for (let i = 1; i <= 50; i++) {
            suitModel.name = i
            suitModel.takePhoto()
        }
      
        // 给模特依次穿上羽绒服,并进行拍照:
        for (let j = 1; j <= 50; j++) {
            jacketModel.name = j
            jacketModel.takePhoto()
        }
        // 这个西装模特对象和一个羽绒服模特对象就是我们的享元了
        // 以上就是是享元模式的雏形

内部状态与外部状态

  • 享元模式要求将对象的属性划分为内部状态与外部 状态(状态在这里通常指属性)。享元模式的目标是尽量减少共享对象的数量,关于如何划分内 部状态和外部状态,下面的几条经验提供了一些指引。
  1. 内部状态存储于对象内部。
  2. 内部状态可以被一些对象共享。
  3. 内部状态独立于具体的场景,通常不会改变。
  4. 外部状态取决于具体的场景,并根据场景而变化,外部状态不能被共享。
  • 这样一来,我们便可以把所有内部状态相同的对象都指定为同一个共享的对象。而外部状态可以从对象身上剥离出来,并储存在外部。

  • 剥离了外部状态的对象成为共享对象,外部状态在必要时被传入共享对象来组装成一个完整的对象。虽然组装外部状态成为一个完整对象的过程需要花费一定的时间,但却可以大大减少系统中的对象数量,相比之下,这点时间或许是微不足道的。因此,享元模式是一种用时间换空间的优化模式。

  • 在上面的例子中,type是内部状态,name是外部状态,通过区分这两种状态,大大减少了系 统中的对象数量。

  • 总结:享元模式共同的属性是内部属性,在创建对象的时候决定;不同的属性是外部属性,在对象创建完后改变。

享元模式的通用结构

  • 示例初步展示了享元模式的威力,但这还不是一个完整的享元模式,在这个例子中 还存在以下两个问题。
  1. 我们通过构造函数显式new出了两个model对象,在其他系统中,也许并不是一开始就需要所有的共享对象。(不用就不创建)

  2. 给 model 对象手动设置了name外部状态,在更复杂的系统中,这不是一个最好的方式,因为外部状态可能会相当复杂,它们与共享对象的联系会变得困难。

  • 我们通过一个对象工厂来解决第一个问题,只有当某种共享对象被真正需要时,它才从工厂中被创建出来。对于第二个问题,可以用一个管理器来记录对象相关的外部状态,使这些外部状态通过某个钩子和共享对象联系起来。

例子

        // 例子
        // 工厂进行对象实例化
        const UploadFactory = (function () {
            const createdFlyWeightObjs = {}
            return {
                create: function (uploadType) {
                    if (createdFlyWeightObjs [uploadType]) {
                        return createdFlyWeightObjs [uploadType]
                    }
                    return createdFlyWeightObjs [uploadType] = new Upload(uploadType)
                }
            }
        })()

        // 管理器封装外部状态
        const uploadManager = (function () {
            // 定义上传文件的集合
            const uploadDatabase = {}
            return {
                // 上传文件处理逻辑
                add: function (id, uploadType, fileName, fileSize) {
                    // 利用UploadFactory.create工厂进行对象实例化,得到实例,同时,uploadType相同的,只会有一个实例
                    // uploadType就是内部状态了,其他属性就是外部状态了
                    const flyWeightObj = UploadFactory.create(uploadType)
                    const 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
                },
                // 用来获取当前id的文件对象
                setExternalState: function (id, flyWeightObj) {
                    const uploadData = uploadDatabase[id]
                    for (const i in uploadData) {
                        flyWeightObj[i] = uploadData[i]
                    }
                }
            }
        })()

        let id = 0
        // 开始上传
        const startUpload = function (uploadType, files) { // uploadType 区分是控件还是 flash
            for (let i = 0; i < files.length; i++) {
                const file = files[i]
                uploadManager.add(++id, uploadType, file.fileName, file.fileSize)
            }
        }
        // 上传的对象
        const Upload = function (uploadType) {
            this.uploadType = uploadType
        }
        Upload.prototype.delFile = function (id) {
            // 这里把id和this传进setExternalState方法,该方法会根据id找到上传的文件对象,并把对象属性赋值给this
            // 那么下面的this.dom就可以找到相应的文件dom了
            uploadManager.setExternalState(id, this) // (1)
            if (this.fileSize < 3000) {
                return this.dom.parentNode.removeChild(this.dom)
            }
            if (window.confirm('确定要删除该文件吗? ' + this.fileName)) {
                return this.dom.parentNode.removeChild(this.dom)
            }
        }
        // 接下来分别创建 3 个插件上传对象和 3 个 Flash 上传对象:
        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: '5.html',
                fileSize: 3000
            },
            {
                fileName: '6.txt',
                fileSize: 5000
            }
        ])

总结

  • 享元模式是为解决性能问题而生的模式,这跟大部分模式的诞生原因都不一样。在一个存在大量相似对象的系统中,享元模式可以很好地解决大量对象带来的性能问题。
  • 优缺点:享元模式是一种用时间换空间的优化模式。

github仓库地址:点击 设计模式例子 查看

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值