定义
享元模式是一种用于性能优化的模式,其核心是运用共享技术来有效支持大量细粒度的对象。
应用场景
- 一个程序中使用了大量的相似对象
- 由于使用了大量对象造成很大的内存开销
- 对象的大多数状态都可以变为外部状态
- 剥离出对象的外部状态之后,可以用相对较少的共享对象取代大量对象
举个🌰
文件上传: 当用户选择了文件之后,通过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++)
}
}
结果
在上述例子中我们可以看到,上传文件的过程中我们创建了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)
}
}