news 2026/4/16 13:48:48

设计模式之-享元模式

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
设计模式之-享元模式

享元模式是一种用于性能优化的模式。

核心是运用共享技术来有效支持大量细粒度的对象。

场景1:假设有个内衣工厂,目前产品有50个男式内衣和50个女式内衣,为了推销产品,需要给这些产品生产一些塑料模特来穿上他们的内衣拍成广告。
来看一个例子,不使用享元模式的情况下:

varModel=function(sex,underwear){this.sex=sex;this.underwear=underwear;}Model.prototype.takePhoto=function(){console.log(`sex=${this.sex}underwear=${this.underwear}`);}for(vari=1;i<=50;i++){varmaleModel=newModel('male',`underwear${i}`);maleModel.takePhoto();}for(varj=1;j<=50;j++){varfemaleModel=newModel('female',`underwear${j}`);femaleModel.takePhoto();}

要得到一张照片,每次需要传入sex和uunderwear参数,综上所述,现在一共有50种男内衣和50种女内衣,所以会产生100个对象,如果将来生产10000种内衣,那这个程序可能因为存在如此多对象已经提前崩溃。

思考下如何优化这个场景,虽然有100种内衣,但很显然不需要50个男模特和50个女模特,其实男女模特只需要各自有一个就行了,他们可以分别穿上不同内衣来拍照。下面来改写下这个代码

varModel=function(sex,underwear){this.sex=sex;// this.underwear = underwear;}Model.prototype.takePhoto=function(){console.log(`sex=${this.sex}underwear=${this.underwear}`);}varmaleModel=newModel('male');varfemaleModel=newModel('female');for(vari=1;i<=50;i++){maleModel.underwear=`underwear${i}`maleModel.takePhoto();}for(varj=1;j<=50;j++){femaleModel.underwear=`underwear${j}`femaleModel.takePhoto();}

可以看到,改进之后的代码,只需要两个对象便完成了同样的功能。

这个例子就是享元模式的雏形,享元模式要求将对象的属性划分为内部状态和外部状态(状态在这里通常指属性),享元模式的目标是尽量减少共享对象的数量。

  1. 内部状态存储于对象内部
  2. 内部状态可以被一些对象共享。
  3. 内部状态独立于具体的场景,通常不会改变。
  4. 外部状态取决于具体的场景,并根据场景而变化,外部状态不能被共享。

在上面的例子中,性别是内部状态,内衣是外部状态,通过区分这两种状态,大大减少了系统中的对象数量,通常来讲,内部状态有多少种组合,系统中便最多存在多少个对象,因为性别通常只有男女,所以该内衣厂商最多只需要2个对象。

这样一来,我们便可以把所有内部状态相同对象指定为同一个共享对象,而外部状态可以从对象身上剥离出来,并存储在外部。

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

下面看下这个例子

varid=0;window.startUpload=function(uploadType,files){// uploadType是区分控件还是flashfor(vari=0,file;file=files[i++];){varuploadObj=newUpload(uploadType,file.fileName,file.fileSize);uploadObj.init(id++);// 给upload对象设置一个唯一的id}}varUpload=function(uploadType,fileName,fileSize){this.uploadType=uploadType;this.fileName=fileName;this.fileSize=fileSize;this.dom=null;}Upload.prototype.init=function(id){varthat=this;this.id=id;this.dom=document.createElement('div');this.dom.innerHTML=`<span>文件名称:${this.fileName},文件大小:${this.fileSize}</span> <button class="delFile">删除</button>`;this.dom.querySelector('.delFile').onclick=function(){that.delFile();}document.body.appendChild(this.dom);}Upload.prototype.delFile=function(){if(this.fileSize<3000){returnthis.dom.parentNode.removeChild(this.dom);}if(window.confirm('确定要删除该文件吗?'+this.fileName)){returnthis.dom.parentNode.removeChild(this.dom);}}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,},]);

这个代码是有多少需要上传的文件,就一共创建了多少个upload对象,接下来我们用享元模式重构他

// 剥离外部状态varUpload=function(uploadType,fileName,fileSize){this.uploadType=uploadType;}Upload.prototype.delFile=function(id){uploadManager.setExternalState(id,this);console.log(this)if(this.fileSize<3000){returnthis.dom.parentNode.removeChild(this.dom);}if(window.confirm('确定要删除该文件吗?'+this.fileName)){returnthis.dom.parentNode.removeChild(this.dom);}}// 工厂进行对象实例化,如果某个内部状态对应的共享对象已经被创建过,那么直接返回这个对象,否则创建一个新对象varUploadFactory=(function(){varcreateFlyWeightObjs={};return{create:function(uploadType){console.log(createFlyWeightObjs[uploadType],createFlyWeightObjs,uploadType)if(createFlyWeightObjs[uploadType]){returncreateFlyWeightObjs[uploadType];}returncreateFlyWeightObjs[uploadType]=newUpload(uploadType);}}})();// 管理器封装外部状态varuploadManager=(function(){varuploadDatabase={};return{add:function(id,uploadType,fileName,fileSize){varflyWeightObj=UploadFactory.create(uploadType);vardom=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,};returnflyWeightObj;},setExternalState:function(id,flyWeightObj){varuploadData=uploadDatabase[id];for(variinuploadData){flyWeightObj[i]=uploadData[i];}}}})();// 然后是开始出发上传动作的startUpload函数varid=0;window.startUpload=function(uploadType,files){// uploadType是区分控件还是flashfor(vari=0,file;file=files[i++];){varuploadObj=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:'5.html',fileSize:3000,},{fileName:'6.txt',fileSize:5000,},]);

享元模式重构之前的代码里一共创建了6个upload对象,而通过享元模式重构之后,对象的数量减少为2,更幸运的是,就算现在同时上传2000个文件,需要创建的upload对象数量依然为2。

没有内部状态的享元

在文件上传的例子中,我们分别进行过插件调用和Flash调用,即startUpload(‘plugin’,[])和startUpload(‘flash’,[]),导致程序中创建了内部状态不同的两个共享对象,也许你会奇怪,在文件上传程序里,一般都会提前通过特性检测来选择一种上传方式,如果浏览器支持插件就用插件,如果不支持插件,那就用flash上传,那么什么情况下既需要插件上传又需要Flash上传呢?
实际上这个需求是存在的,很多网盘都提供了极速上传(控件)与普通上传(Flash)两种模式,如果极速上传不好使(可能没有安装控件或者控件损坏),用户还可以随时切换到普通上传模式,这里确实需要同时存在两个不同的upload共享对象。
但并不是每个网站都必须做的如此复杂,很多小一些的网站就支持单一的上传方式,假设我们是这个网站的开发中,不需要考虑极速上传与普通上传之间的切换,这意味着在之前的代码中作为内部状态的uploadType属性是可以删除的
在继续使用享元模式的前提下,构造函数Upload就变成了无参数的形式。

varUpload=function(){}

其他属性如fileName、fileSize,dom依然可以作为外部状态保存在共享对象外部,在uploadType作为内部状态的时候,他可能为控件,也可能为Flash,所以当时最多可以组合出两个共享对象。而现在已经没有了内部状态,这意味着只需要唯一的一个共享对象。现在我们要改写创建享元对象的工厂,代码如下:

varUploadFactory=(function(){varuploadObj;return{create:function(){if(uploadObj){returnuploadObj;}returncuploadObj=newUpload();}}})();

管理器部分的代码不需要改动,还是负责剥离和组装外部状态,可以看到,当对象没有内部状态的时候,生产共享对象的工厂实际上变成了一个单例工厂。虽然这时候的共享对象没有了内部状态的区分,但还是有剥离外部状态的过程,我们依然倾向于称之为享元模式。

非原创,来源javascript设计模式与开发实践 -曾探

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/15 21:25:08

Docker在测试环境中的应用:效率、一致性与敏捷性的变革

在软件交付周期日益缩短、技术栈日趋复杂的今天&#xff0c;测试环境的稳定性、一致性与快速部署能力&#xff0c;已成为决定测试效能与发布质量的关键瓶颈。传统的物理机或虚拟机环境&#xff0c;常因配置差异、资源争用和启动缓慢等问题&#xff0c;导致“在我机器上是好的”…

作者头像 李华
网站建设 2026/4/16 11:02:36

Kubernetes上的测试:挑战与解决方案

测试范式的转变 Kubernetes已成为云原生应用事实上的部署与运行标准。其带来的自动扩缩容、滚动更新、声明式配置等特性&#xff0c;在提升运维效率和资源利用率的同时&#xff0c;也彻底改变了应用的运行态。对于测试团队而言&#xff0c;这意味着测试对象从一个相对静态的“…

作者头像 李华
网站建设 2026/4/16 11:02:33

如何在个人电脑部署Open-AutoGLM:从环境配置到成功运行全记录

第一章&#xff1a;Open-AutoGLM 本地部署概述Open-AutoGLM 是一个开源的自动化代码生成与推理框架&#xff0c;基于 GLM 架构实现本地化智能编程辅助。该系统支持代码补全、函数生成、错误修复等功能&#xff0c;适用于开发者在隔离环境中构建智能化开发流程。通过本地部署&am…

作者头像 李华
网站建设 2026/4/16 13:05:40

RRT*算法与三次 B 样条函数在机械臂轨迹避障中的应用

一种采用RRT*机械臂轨迹避障算法&#xff0c;然后采用三次B 样条函数对 所 规 划 路 径 进 行 拟 合 优 化。 带有较为详细的注视 rrt路径规划结合机械臂仿真 基于matlab&#xff0c;6自由度&#xff0c;机械臂rrt算法路径规划&#xff0c;输出如下效果&#xff0c;直接运行即可…

作者头像 李华
网站建设 2026/4/13 2:58:44

如何利用有限的数据发表更多的SCI论文?——利用ArcGIS探究环境和生态因子对水体、土壤和大气污染物的影响

SCI的写作和发表是科研人提升自身实力和实现自己价值的必要途径。“如何利用有限的数据发表更多的SCI论文&#xff1f;”是我们需要解决的关键问题。一&#xff1a;ARCGIS软件的基本介绍和如何获取空间数据1. ArcGIS软件初识与如何获取空间数据&#xff1a;1.1 ArcCatalog、Arc…

作者头像 李华
网站建设 2026/4/16 13:07:46

uni-app 项目在 iOS 上架过程中常见的问题与应对方式

在 uni-app 项目里&#xff0c;开发阶段通常推进得很顺。页面逻辑、接口对接、跨端兼容&#xff0c;一旦跑通&#xff0c;团队很容易形成一种判断&#xff1a;“剩下的就是打包和上架了。” 但真正进入 App Store 上架流程后&#xff0c;很多问题才开始出现&#xff0c;而且这些…

作者头像 李华