享元模式(Flyweight),运用共享技术有效地支持大量细粒度的对象。

内部状态与外部状态

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

享元模式是一种用时间换空间的优化模式

  • 可以被对象共享的属性通常被划分为内部状态
  • 外部状态取决于具体场景,并根据场景而变化

下面给出上面实现代码中的类图,如下所示:

在上图中,涉及的角色如下几种角色:

抽象享元角色(Flyweight):此角色是所有的具体享元类的基类,为这些类规定出需要实现的公共接口。那些需要外部状态的操作可以通过调用方法以参数形式传入。

具体享元角色(ConcreteFlyweight):实现抽象享元角色所规定的接口。如果有内部状态的话,可以在类内部定义。

享元工厂角色(FlyweightFactory):本角色复杂创建 和管理享元角色。本角色必须保证享元对象可以被系统适当地共享,当一个客户端对象调用一个享元对象的时候,享元工厂角色检查系统中是否已经有一个符合要求 的享元对象,如果已经存在,享元工厂角色就提供已存在的享元对象,如果系统中没有一个符合的享元对象的话,享元工厂角色就应当创建一个合适的享元对象。

客户端角色(Client):本角色需要存储所有享元对象的外部状态。

注:上面的实现只是单纯的享元模式,同时还有复合的享元模式,由于复合享元模式较复杂,这里就不给出实现了。

C#享元模式:

namespace 享元模式
{
class Program
{
static void Main(string[] args)
{ WebSiteFactory f = new WebSiteFactory(); WebSite fx = f.GetWebSiteCategory("产品展示");
fx.Use(new User("小菜")); WebSite fy = f.GetWebSiteCategory("产品展示");
fy.Use(new User("大鸟")); WebSite fz = f.GetWebSiteCategory("产品展示");
fz.Use(new User("娇娇")); WebSite fl = f.GetWebSiteCategory("博客");
fl.Use(new User("老顽童")); WebSite fm = f.GetWebSiteCategory("博客");
fm.Use(new User("桃谷六仙")); WebSite fn = f.GetWebSiteCategory("博客");
fn.Use(new User("南海鳄神")); Console.WriteLine("得到网站分类总数为 {0}", f.GetWebSiteCount()); //string titleA = "大话设计模式";
//string titleB = "大话设计模式"; //Console.WriteLine(Object.ReferenceEquals(titleA, titleB)); Console.Read();
}
} //用户
public class User
{
private string name; public User(string name)
{
this.name = name;
} public string Name
{
get { return name; }
}
} //网站工厂
class WebSiteFactory
{
private Hashtable flyweights = new Hashtable(); //获得网站分类
public WebSite GetWebSiteCategory(string key)
{
if (!flyweights.ContainsKey(key))
flyweights.Add(key, new ConcreteWebSite(key));
return ((WebSite)flyweights[key]);
} //获得网站分类总数
public int GetWebSiteCount()
{
return flyweights.Count;
}
} //网站
abstract class WebSite
{
public abstract void Use(User user);
} //具体的网站
class ConcreteWebSite : WebSite
{
private string name = "";
public ConcreteWebSite(string name)
{
this.name = name;
} public override void Use(User user)
{
Console.WriteLine("网站分类:" + name + " 用户:" + user.Name);
}
}
}

文件上传的例子

var  id = 0;

window.startUpload = function(uploadType,files){   //uploadType区分是控件还是flash
for(var i = 0, file; file=files[i++];){
var uploadObj = new Upload(uploadType,file.fileName,file.fileSize);
uploadObj.init(id++); //给upload对象设置一个唯一的id
}
}; var Upload = function(uploadType,fileName,fileSize){
this.uploadType = uploadType;
this.fileName = fileName;
this.fileSize = fileSize;
this.dom = null;
}; Upload.prototype.init = function(id){
var that = 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.addendChild(this.dom);
}; Upload.prototype.delFile = function(){
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.htm',
fileSize: 3000
},
{
fileName: '3.txt',
fileSize: 5000
}
]); startUpload('flash',[
{
fileName: '4.txt',
fileSize: 1000
},
{
fileName: '5.htm',
fileSize: 3000
},
{
fileName: '6.txt',
fileSize: 5000
}
]);

享元模式重构文件上传

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

首先,我们需要确认插件类型upload是内部状态,那为什么单单uploadType是内部状态呢?前面已经讲过。

旦明确了uploadType,无论我们使用什么方式上传,这个上传对象都是可以被任何文件共用的。而fileName和fileSize是根据场景而变
化的,每个文件的fileName和fileSize都不一样,fileName和fileSize没有办法被共享,它们只能被划分为外部状态。

明确了uploadType作为内部状态后,我们再把其他的外部状态从构造函数中抽离出来,Upload构造函数中只保留uploadType参数:

var Upload = function(uploadType){
this.uploadType = uploadType;
};

Upload.prototype.init函数也不再需要,因为upload对象初始化的工作被放在了uploadManager.add函数里, 接下来只需要定义Upload.prototype.del函数即可:

Upload.prototype.delFile = function(id){
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);
}
};

在 开始删除文件之前,需要读取文件的实际大小,而文件的实际大小被存储在外部管理器uploadManager中,所以在这里需要通过 uploadManager.setExternalState方法给共享对象设置正确的fileSize,上段代码中的(1)处表示把当前id对应的对 象的外部状态都组装到共享对象中。

接下来定义一个工厂来创建upload对象,如果某种内部状态对应的共享对象已经被创建过,那么直接返回这个对象,否则创建一个新的对象:

var UploadFactory = (function(){
var createFlyWeightObjs = {}; return{
create:function(uploadType){
if(createFlyWeightObjs[uploadType]){
return createFlyWeightObjs[uploadType];
}
return createFlyWeightObjs[uploadType] = new Upload(uploadType);
}
}
})();

现在我们来完善前面提到的uploadManager对象,它负责向UploadFactory提交创建对象的请求,并用一个uploadDatabase对象保存所有upload对象的外部状态,以便在程序运行过程中给upload共享对象设置外部状态,代码如下:

var uploadManager = (function(){
var uploadDatabase = {}; return{
add:function(){id,uploadType,fileName,fileSize){
var flyWeightObj = UploadFactory.create(uploadType); var 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;
},
setExternalState:function(id,flyWeightObj){
var uploadData = uploadDatabase[id];
for(var i in uploadData){
flyWeightObj[i] = uploadData[i];
}
}
}
})();

然后是开始触发上传动作的startUpload函数:

var id = 0;

window.startUpload = function(uploadType,files){
for(var i=0,file;file=files[i++];){
var uploadObj = uploadManager.add(++id,uploadType,file.fileName,file.fileSize);
}
};

最后是测试时间,运行下面的代码后,可以发现运行结果跟用享元模式重构之前一致:

startUpload('plugin',[
{
fileName: '1.txt',
fileSize: 1000
},
{
fileName: '2.htm',
fileSize: 3000
},
{
fileName: '3.txt',
fileSize: 5000
}
]); startUpload('flash',[
{
fileName: '4.txt',
fileSize: 1000
},
{
fileName: '5.htm',
fileSize: 3000
},
{
fileName: '6.txt',
fileSize: 5000
}
]);

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

享元模式的适用性

享元模式是一种很好的性能优化方案,但它也会带来一些复杂的问题,从前面两组代码的比较可以看到,使用了享元模式之后,我们需要分别多维护一个factory对象和一个manager对象,在大部分不必要使用享元模式的环境下,这些开销是可以避免的。

享元模式带来的好处很大程度上取决于如何使用以及何时使用,一般来说,以下情况发生时便可以使用享元模式。

1.一个程序中使用了大量的相似对象
2.由于使用了大量对象,造成很大的内存开销。
3.对象的大多数状态都可以变为外部状态
4.剥离出对象的外部状态之后,可以用相对较少的共享对象取代大量对象。

没有内部状态的享元

在继续使用享元模式的前提下,构造函数Upload就变成了无参数的形式:

var Upload = function(){};

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

var UploadFactory = (function(){
var uploadObj;
return{
create:function(){
if(uploadObj){
return uploadObj;
}
return uploadObj = new Upload();
}
}
})();

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

js享元模式的更多相关文章

  1. js设计模式-享元模式

    享元模式实际上是一种优化模式,目的在于提高系统的性能和代码的效率. 使用享元模式的条件:最重要的条件是网页中必须使用了大量资源密集型对象,如果只会用到了少许这类对象,那么这种优化并不划算.第二个条件是 ...

  2. JS常用的设计模式(16)—— 享元模式

    享元模式主要用来减少程序所需的对象个数. 有一个例子, 我们这边的前端同学几乎人手一本<JavaScript权威指南>. 从省钱的角度讲, 大约三本就够了. 放在部门的书柜里, 谁需要看的 ...

  3. js设计模式(8)---享元模式

    0.前言 今天总结了四种设计模式,到现在有点精疲力尽了,但是还是有不少收获,很开心自己有掌握了新的东西,今天变得有了价值. 1.使用条件 1.1.网页中使用了大量资源密集型的对象: 1.2.这些对象中 ...

  4. JS设计模式(9)享元模式

    什么是享元模式? 定义:享元模式是一种优化程序性能的模式,本质为减少对象创建的个数. 主要解决:在有大量对象时,有可能会造成内存溢出,我们把其中共同的部分抽象出来,如果有相同的业务请求,直接返回在内存 ...

  5. 浅谈js设计模式 — 享元模式

    享元(flyweight)模式是一种用于性能优化的模式,“fly”在这里是苍蝇的意思,意为蝇量级.享元模式的核心是运用共享技术来有效支持大量细粒度的对象. 假设有个内衣工厂,目前的产品有 50种男式内 ...

  6. javascript 设计模式-----享元模式

    四个轮子,一个方向盘,有刹车,油门,车窗,这些词首先让人联想到的就是一辆汽车.的确,这些都是是一辆车的最基本特征,或者是属性,我们把词语抽象出来,而听到这些词语的人把他们想象陈一辆汽车.在代码里面也是 ...

  7. JAVA 设计模式 享元模式

    用途 享元模式 (Flyweight) 运用共享技术有效地支持大量细粒度的对象. 享元模式是一种结构型模式. 结构

  8. 利用享元模式来解决DOM元素过多导致的网页解析慢、卡死的问题

    我也不知道应该为本文的思路取一个什么比较恰当的标题,但是感觉符合享元模式的思路. 在一些网页应用中,有时会碰到一个超级巨大的列表,成千上万行,这时大部份浏览器解析起来就非常痛苦了(有可能直接卡死). ...

  9. Javascript设计模式理论与实战:享元模式

    享元模式不同于一般的设计模式,它主要用来优化程序的性能,它最适合解决大量类似的对象而产生的性能问题.享元模式通过分析应用程序的对象,将其解析为内在数据和外在数据,减少对象的数量,从而提高应用程序的性能 ...

随机推荐

  1. Machine Learning - week 2 - Multivariate Linear Regression

    Multiple Features 上一章中,hθ(x) = θ0 + θ1x,表示只有一个 feature.现在,有多个 features,所以 hθ(x) = θ0 + θ1x1 + θ2x2 + ...

  2. EChars文档

    http://echarts.baidu.com/echarts2/doc/doc.html#SeriesMap http://echarts.baidu.com/option.html

  3. MySQL中行锁的算法

    行锁的3中算法 Record Lock:单个行记录上的锁 Gap Lock:间隙锁,锁定一个范围,但不包含记录本身 Next-key Lock:Gap Lock+Record Lock锁定一个范围,并 ...

  4. C语言中的const,free使用方法具体解释

    注意:C语言中的const和C++中的const是有区别的,并且在使用VS编译測试的时候. 假设是C的话.请一定要建立一个后缀为C的文件.不要是CPP的文件. 由于.两个编译器会有区别的. 一.C语言 ...

  5. .NET Framework 3.5-8 下载地址

    https://dotnet.microsoft.com/download/dotnet-framework Version Released End of life .NET Framework 4 ...

  6. Java并发—线程池框架Executor总结(转载)

    为什么引入Executor线程池框架 new Thread()的缺点 每次new Thread()耗费性能 调用new Thread()创建的线程缺乏管理,被称为野线程,而且可以无限制创建,之间相互竞 ...

  7. boost之正确性和测试

    BOOST_ASSERT在debug模式下有效. #include <iostream> #include <boost/assert.hpp> using namespace ...

  8. 【转】Python爬虫(3)_Beautifulsoup模块

    一 介绍 Beautiful Soup 是一个可以从HTML或XML文件中提取数据的Python库.它能够通过你喜欢的转换器实现惯用的文档导航,查找,修改文档的方式.Beautiful Soup会帮你 ...

  9. 20160418 while,switch,do..while的使用

    9 一.While循环 示例:求100以内所有数的和 Int i=1;//初始条件 Int sum=0; While(i<=100)//循环条件 { Sum+=i;//循环体 i++;//状态改 ...

  10. hostname -f 失败解决办法

    $ hostname fzk $ uname -n fzk 当 hostname -f 时报错:未搜索到主机名 产生这个原因时因为  /etc/hosts和/etc/sysconfig/network ...