文档章节

享元模式

o
 onedotdot
发布于 2017/08/28 21:34
字数 1931
阅读 3
收藏 0
点赞 0
评论 0

享元,就是共享元素的意思。

先来看一个生活中的案例:

假如你是个开网店的老板,进了50款男装和50款女装。为了赶上双十一能有更好的销量,你想花点钱雇模特来穿上你的衣服拍成广告照片。这时问题来了:

你到底是雇佣100个模特穿这100款衣服呢?

还是只雇一男一女2个模特,分别穿50款衣服呢?

得,这不是什么脑筋急转弯。正常人的选择肯定是男女2个人,而不是100人。

为啥啊?那不是废话吗?我花100个人的钱,这开销得多大啊!

ok,这其实就是个享元模式的真实案例。

享元模式,就是将对象划分为内部状态和外部状态,从而减少共享对象数量的一种模式。

很抽象是吧。好吧,那咱还是说人话来解释。

我们再看下这个雇模特的例子。别笑,我们做一下分析。

按理说,一款衣服配一个人,最为简单直观,一个萝卜一个坑,但这花销实在太大了。为了省钱,就要找出可以共享的元素来。

一共有衣服和人两个元素,每款衣服都是不一样的,所以衣服这个元素肯定是无法被共享的;由此得出被共享的元素是人,也就是只需男女两人即可。

那么我们就说,这个享元模式的内部状态就是人的性别,而外部状态就是衣服款式。从代码角度讲,我们最低限度是造出男女两个对象出来,这个是无法再省了。

再回过头看看享元模式的解释,是不是已经明白了?

下面我们用代码模拟一下。

无模式的模特穿衣:

<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>无模式</title>
</head>
<body>

</body>
</html>
<script>
//雇佣模特
var HireModel = function(sex,clothes){
this.sex = sex;
this.clothes = clothes;
};

HireModel.prototype.wearClothes = function(){
var div = document.createElement('div');
var str = this.sex+"穿了"+this.clothes;
var text = document.createTextNode(str);
div.appendChild(text);
document.body.appendChild(div);
};
/*******通过运行时间测试性能**********/
var start = new Date().getTime();//起始时间
for(var i=0;i<50000;i++){
var model = new HireModel('male','第'+i+'款');
model.wearClothes();

}
var end = new Date().getTime();//结束时间
console.log(end-start);
</script>

享元模式的模特穿衣:

<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>享元模式</title>
</head>
<body>

</body>
</html>
<script>
//雇佣模特
var HireModel = function(sex){
//内部状态是性别
this.sex = sex;
};
HireModel.prototype.wearClothes = function(clothes){
var div = document.createElement('div');
var str = this.sex+"穿了"+clothes;
var text = document.createTextNode(str);
div.appendChild(text);
document.body.appendChild(div);
};
//工厂模式,负责造出男女两个模特
var ModelFactory = (function(){
var cacheObj = {};
return {
create:function(sex){
//根据sex分组
if(cacheObj[sex]){
return cacheObj[sex];
}else{
cacheObj[sex] = new HireModel(sex);
return cacheObj[sex];
  }
 }
};
})();
//模特管理
var ModelManager = (function(){
//容器存储:1.共享对象 2.外部状态
var vessel = {};
return {
add:function(sex,clothes,id){
//造出共享元素:模特
var model = ModelFactory.create(sex);
//以id为键存储所有状态
vessel[id] = {
model:model,
clothes:clothes
};
},
wear:function(){
for(var key in vessel){
//调用雇佣模特类中的穿衣服方法。
vessel[key]['model'].wearClothes(vessel[key]['clothes']);
  }
}
};
})();
/*******通过运行时间测试性能**********/
var start = new Date().getTime();//起始时间
for(var i=0;i<50000;i++){
ModelManager.add('male','第'+i+'款',i);
}
var end = new Date().getTime();//结束时间
console.log(end-start);
ModelManager.wear();
</script>

我们将内部状态划分出来后,就可以将原类进行瘦身了。

原类中的方法如果要用到外部状态,都变为了参数;而真实的外部状态被分离出来后,被打到了专门一个管理器下,由这个管理器统一调度使用。

管理器定义了一个容器,专门存储所有的外部状态;而可以被共享的元素,就用工厂方法造出,其实这也是个一对多的模型。

这样处理会增加一些复杂度,多维护了一个工厂方法和调度管理类,但经过测试我们发现,在5万数量级的压力测试下,享元模式的实际运行时间比无模式下要短的好多。

这里有个坑容易掉进去。

我们用了享元模式后会有一种错觉,仿佛无论循环多少次,因自始至终都是创建了2个对象,所以内存消耗总是非常小的,都应该秒出才对,但大家实际测试会发现,当数量级调整到5百万时,却又非常的慢,这是为啥?

这里大家要注意一下。我们通过工厂模式,确实只造出来2个对象是没错,但我们在ModelManager这个类中,实际是开辟了一大块内存来存储所有的外部状态。为啥是一大块呢?

5百万数量级对应的,就是5百万个外部状态,相应需要容纳5百万个对象的存储空间,这对内存来说肯定是巨大的开支,这在有模式还是无模式下负担都很重。

那享元模式的提升效率体现在哪里?

知道我为啥在wearClothes()方法加了dom操作吗?

因为dom操作都是非常耗时的。同样的dom操作在运行时与前者相比,因为共享2个对象,享元模式肯定要快的多,这才是其优势体现。

当然咯,我们在实际工作中都应该极力避免频繁的dom操作,能共享的dom就不要创建第二次;进一步说,能共享的对象就不要创建第二次,由此我们引出了对象池的概念。

对象池是享元模式的一个变种。与传统的享元相比,它并没有什么内部和外部状态的划分,只是共享。所以相对简单的多,使用场景也更广泛。

对象池很容易理解,比如百度地图上标记地名的小气泡。

如果搜索我们公司附近时,页面出现了2个气泡;这时我再缩小搜索范围,页面就出现了5个气泡。在第二次搜索时,并不会把第一次创建的2个气泡删除,而是放进了对象池,第二次搜索时只需创建3个气泡即可。我们下面试着模拟一下。

<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>享元模式-对象池</title>
</head>
<body>
<button onclick="firstSearch()">第一次搜索</button>
<button onclick="recoverTip()">回收气泡</button>
<button onclick="secondSearch()">第二次搜索</button>
</body>
</html>
<script>
var domFactory = (function(){
var pool = [];
return {
create:function(){
if(pool.length == 0){
var div = document.createElement('div');
document.body.appendChild(div);
return div;
}else{
//从对象池取出一个dom
return pool.shift();
}
},
recover:function(dom){
//将dom推入对象池
pool.push(dom);
}
};
})();

var arr = [];
//第一次搜索的两个气泡
function firstSearch(){
var tipArr = ['A','B'];
for(var i=0;i<tipArr.length;i++){
var tip = domFactory.create();
tip.innerHTML = tipArr[i];
document.body.appendChild(tip);
arr.push(tip);
}
}

//把搜索过的dom节点回收到对象池
function recoverTip(){
for(var i=0;i<arr.length;i++){
domFactory.recover(arr[i]);
}
}
//第二次搜索的5个气泡
function secondSearch(){
var tipArr2 = ['A','B','C','D','E'];
for(var i=0;i<tipArr2.length;i++){
var newtip = domFactory.create();
newtip.innerHTML = tipArr2[i];
document.body.appendChild(newtip);
}
}

</script>

运行效果:

从此例中可以看出,如果我不点击回收按钮,就会重新创建新的5个气泡;而点了回收,就会从上次搜索的基础上添加3个气泡,极大的节省了开销。

享元模式是为解决性能问题而生的模式。与单例模式相比,在复用角度上思想是一致的。但二者不同点也非常多,大家需要多玩味比较一下,千万别混为一谈。

大家可以关注我的微信号:"前端js动力节点",浏览更精彩的内容。

 

 

本文转载自:https://zhuanlan.zhihu.com/p/24454483

共有 人打赏支持
o
粉丝 6
博文 320
码字总数 14350
作品 0
朝阳

暂无文章

前端基础

1. get请求传参长度的误区 误区:我们经常说get请求参数的大小存在限制,而post请求的参数大小是无限制的。 实际上HTTP 协议从未规定 GET/POST 的请求长度限制是多少。对get请求参数的限制是...

wenxingjun
今天
0
0
Android 复制和粘贴功能

做了一回搬运工,原文地址:https://blog.csdn.net/kennethyo/article/details/76602765 Android 复制和粘贴功能,需要调用系统服务ClipboardManager来实现。 ClipboardManager mClipboardM...

她叫我小渝
今天
0
0
拦截SQLSERVER的SSL加密通道替换传输过程中的用户名密码实现运维审计(一)

工作准备 •一台SQLSERVER 2005/SQLSERVER 2008服务 •SQLSERVER jdbc驱动程序 •Java开发环境eclipse + jdk1.8 •java反编译工具JD-Core 反编译JDBC分析SQLSERVER客户端与服务器通信原理 SQ...

紅顏為君笑
今天
6
0
jQuery零基础入门——(六)修改DOM结构

《jQuery零基础入门》系列博文是在廖雪峰老师的博文基础上,可能补充了个人的理解和日常遇到的点,用我的理解表述出来,主干出处来自廖雪峰老师的技术分享。 在《零基础入门JavaScript》的时...

JandenMa
今天
0
0
linux mint 1.9 qq 安装

转: https://www.jianshu.com/p/cdc3d03c144d 1. 下载 qq 轻聊版,可在百度搜索后下载 QQ7.9Light.exe 2. 去wine的官网(https://wiki.winehq.org/Ubuntu) 安装 wine . 提醒网页可以切换成中...

Canaan_
今天
0
0
PHP后台运行命令并管理运行程序

php后台运行命令并管理后台运行程序 class ProcessModel{ private $pid; private $command; private $resultToFile = ''; public function __construct($cl=false){......

colin_86
今天
1
0
数据结构与算法4

在此程序中,HighArray类中的find()方法用数据项的值作为参数传递,它的返回值决定是否找到此数据项。 insert()方法向数组下一个空位置放置一个新的数据项。一个名为nElems的字段跟踪记录着...

沉迷于编程的小菜菜
今天
1
1
fiddler安装和基本使用以及代理设置

项目需求 由于开发过程中客户端和服务器数据交互非常频繁,有时候服务端需要知道客户端调用接口传了哪些参数过来,这个时候就需要一个工具可以监听这些接口请求参数,已经接口的响应的数据,这种...

银装素裹
今天
0
0
Python分析《我不是药神》豆瓣评论

读取 Mongo 中的短评数据,进行中文分词 对分词结果取 Top50 生成词云 生成词云效果 看来网上关于 我不是药神 vs 达拉斯 的争论很热啊。关于词频统计就这些,代码中也会完成一些其它的分析任...

猫咪编程
今天
0
0
虚拟机怎么安装vmware tools

https://blog.csdn.net/tjcwt2011/article/details/72638977

AndyZhouX
昨天
1
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部