js设计模式-享元模式

244 阅读1分钟
定义

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

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

文件上传: 当用户选择了文件之后,通过flash上传或者插件长传方式上传文件

代码
// main.vue
<template>
    <button @click="upload">上传</button>
    <div :id="`upload${index}`" v-for="(item, index) in pluginList" :key="index"></div>
    <div :id="`upload${index + pluginList.length}`" v-for="(item, index) in flashList" :key="index"></div>
</template>

<script lang="ts">
import { startUpload } from '@/utils/flyweight'
import { defineComponent } from 'vue'
export default defineComponent({
  data() {
    return {
      pluginList: [{
        fileName: '1.txt',
        fileSize: 1000,
      },{
        fileName: '2.txt',
        fileSize: 3000,
      },{
        fileName: '3.txt',
        fileSize: 5000,
      }],
      flashList: [{
        fileName: '4.txt',
        fileSize: 1000,
      },{
        fileName: '5.txt',
        fileSize: 3000,
      },{
        fileName: '6.txt',
        fileSize: 5000,
      }]
    }
  },
  methods: {
    upload() {
      startUpload('plugin', this.pluginList)
      startUpload('flash', this.flashList)
    }
   }
})
</script>
// temp.vue
<template>
<div>
  <span>文件名称: {{fileName}} 文件大小: {{fileSize}}</span>
  <button @click="delFile">删除文件</button>
</div>
</template>

<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
  props: {
    fileName: String,
    fileSize: String,
    delFile: Function
  }
})
</script>
// flyweight.ts
import { createApp, h } from 'vue'
import Tem from './tem.vue'

interface File {
  fileName: string,
  fileSize: number
}

class Upload {
  public uploadType
  public fileName
  public fileSize
  public id
  static dom
  constructor(uploadType: string, fileName: string, fileSize: number) {
    this.uploadType = uploadType;
    this.fileName = fileName;
    this.fileSize = fileSize;
    this.id = 0;
  }

  init(id: number) {  // 初始化上传元素
    this.id = id;
    const that = this;
    const app = createApp({
      render: ()=> {
        return h(
          Tem,
          {
            fileName: this.fileName,
            fileSize: this.fileSize,
            delFile: () => {this.delFile()},
          }
        )
      }
    })
    that.dom = app;
    app.mount(`#upload${id}`)
  }

  delFile() { // 删除文件
    if (this.fileSize < 3000) {
      this.dom.unmount()
      return
    }
    if (window.confirm(`确定删除该文件吗?${this.fileName}`)) {
      this.dom.unmount()
    }
  }
}


let id = 0;
export const startUpload = (uploadType: string, files: File[]) => {
  for (let i = 0; i < files.length; i++) {
    const file = files[i];
    const uploadObj = new Upload(uploadType, file.fileName, file.fileSize) // 每上传一个文件都要新增一个上传对象
    uploadObj.init(id++)
  }
}
结果

image.png 在上述例子中我们可以看到,上传文件的过程中我们创建了3个插件上传对象和3个Flag上传对象,现在我们使用享元模式来重构它。
首先,我们需要分辨出哪些是外部状态,哪些是内部状态,其划分关键主要是以下几点:

  • 内部状态存储于对象内部
  • 内部状态可以被一些对象共享
  • 内部状态独立于具体的场景,通常不会改变
  • 外部状态取决于具体的场景,并根据场景而变化,外部状态不能共享
    根据以上条件,我们可以看出:upload对象必须依赖uploadType属性才能工作,只要我们明确了uploadType,这个上传对象都是可以被任何文件共用的。所以uploadType是内部状态;而fileName、fileSize以及dom元素是根据场景而变化的,每个文件都不一样,它们没有办法被共享,它们只能被划分为外部状态
重构代码
// flyweight.ts 其他文件不变,重构flyweight.ts
import { createApp, h } from 'vue'
import Tem from './tem.vue'

interface File {
  fileName: string,
  fileSize: number
}

interface FlyWeightObj {
  fileName: string,
  fileSize: number
  dom: any
}

class Upload { // 上传对象类
  public uploadType // 抽取内部状态
  public flyWeightObj: FlyWeightObj // 剥离外部状态
  constructor(uploadType: string) {
    this.uploadType = uploadType;
    this.flyWeightObj = {
      fileSize: 0,
      fileName: '',
      dom: {}
    }
  }

  delFile(id: number) { // 删除文件
    // 从外部管理器中获取当前文件的外部状态
    UploadManager.setExternalState(id, this)
    const { fileSize, fileName, dom  } = this.flyWeightObj;
    if (fileSize < 3000) {
      return dom.unmount()
    }
    if (window.confirm(`确定删除该文件吗?${fileName}`)) {
      dom.unmount()
    }
  }
}

class UploadFactory { // 上传对象根据上传类型进行工厂实例化
  static createFlyWeights = {}
  static create(uploadType: string) {
    // 只需要根据上传方式创建不同的上传对象,在该例中我们只需要创建两个Upload对象
    if (this.createFlyWeights[uploadType]) {
      return this.createFlyWeights[uploadType]
    }
    return this.createFlyWeights[uploadType] = new Upload(uploadType)
  }
}


class UploadManager { // 管理器封装外部状态
  static uploadDatabase = {}
  static add(id: number, uploadType: string, fileName: string, fileSize: number) {
    const flyWeightObj = UploadFactory.create(uploadType)
    const app = createApp({
      render: ()=> {
        return h(
          Tem,
          {
            fileName: fileName,
            fileSize: fileSize,
            delFile: () => {flyWeightObj.delFile(id)},
          }
        )
      }
    })
    app.mount(`#upload${id}`)
    this.uploadDatabase[id] = {
      fileName,
      fileSize,
      dom: app
    }
    return flyWeightObj
  }

  static setExternalState(id:number, uploadObj: Upload) {
    const uploadData = this.uploadDatabase[id];
    uploadObj.flyWeightObj = uploadData
  }
}

let id = 0;
export const startUpload = (uploadType: string, files: File[]) => {
  for (let i = 0; i < files.length; i++) {
    const file = files[i];
    const uploadObj = UploadManager.add(id++, uploadType, file.fileName, file.fileSize)
  }
}