这是我参与8月更文挑战的第9天,活动详情查看:8月更文挑战
享元(flyweight)模式是一种用于性能优化的模式,“fly”在这里是苍蝇的意思,意为蝇量级。享元模式的核心是运用共享技术来有效支持大量细粒度的对象。
初识享元模式
假设有个衣服工厂,目前的产品有50种男士衣服和50种女士衣服,为了推销产品,工厂决定生产一些塑料模特来穿上衣服拍成广告照片。正常情况下需要50个男模特和50个女模特,然后分别让他们每人分别穿上一件衣服来拍照。不使用享元模式的情况下,在程序里会这样写:
var Model = function (sex, cloth) {
this.sex = sex; this.cloth = cloth;
};
Model.prototype.takePhoto = function () {
console.log('sex= ' + this.sex + ' cloth=' + this.cloth);
};
for (var i = 1; i <= 50; i++) {
var maleModel = new Model('male', 'cloth' + i);
maleModel.takePhoto();
};
for (var j = 1; j <= 50; j++) {
var femaleModel = new Model('female', 'cloth' + j);
femaleModel.takePhoto();
};
要得到一张照片,每次都需要传入 sex 和 cloth 参数,如上所述,会产生100个对象,如果将来生产了10000种衣服,那么这个程序可能会因为存在如此多的对象提前崩溃。
来考虑下如何优化,其实并不需要50个男模特和50个女模特,只需要一个男模特和一个女模特即可,他们分别穿上不同的衣服就可以。
那么现在来改写代码,只需要区别男女模特。
var Model = function (sex) {
this.sex = sex;
};
Model.prototype.takePhoto = function () {
console.log('sex= ' + this.sex + ' cloth=' + this.cloth);
};
分别创建一个男模特对象和一个女模特对象:
var maleModel = new Model('male'),
femaleModel = new Model('female');
给男模特依次穿上男装来拍照:
for (var i = 1; i <= 50; i++) {
maleModel.cloth = 'cloth' + i;
maleModel.takePhoto();
};
同样,给女模特依次穿上所有的女装,拍照:
for (var j = 1; j <= 50; j++) {
femaleModel.cloth = 'cloth' + j;
femaleModel.takePhoto();
};
可以看到,改进之后,只需要两个对象便完成了同样的功能。
内部状态与外部状态
享元模式要求将对象的属性划分为内部状态与外部状态,享元模式的目标是尽量减少共享对象的数量。
- 内部状态存储于对象内部
- 内部状态可以被一些对象共享
- 内部状态独立于具体的场景,通常不会改变
- 外部状态取决于具体的场景,并根据场景而变化,外部状态不能被共享
这样的话,就可以把所有内部状态相同的对象都指定为同一个共享的对象。而外部状态可以从对象身上剥离出来,并储存在外部。
享元模式的通用结构
在上述的示例中,还存在以下两个问题:
- 通过构造函数显式地
new出了男女两个model对象,在其他系统中,也许并不是一开始就需要所有的共享对象。 - 给
model对象手动设置了cloth外部状态,在更复杂的系统中,这不是一个最好的方式,因为外部状态可能会相当复杂,他们与共享对象的联系会变的困难。
通过一个对象工厂来解决第一个问题,只有当某种共享对象被真正需要时,它才从工厂中被创建出来。对于第二个问题,可以用一个管理器来记录对象相关的外部状态,使这些外部状态通过某个钩子和共享对象联系起来。
享元模式的适用性
享元模式是一种很好的性能优化方案,但它也会带来一些复杂性的问题,一般来说,以下情况发生时便可以使用享元模式。
- 一个程序中使用了大量的相似对象。
- 由于使用了大量对象,造成很大的内存开销。
- 对象的大多数状态都可以变为外部状态。
- 剥离出对象的外部状态之后,可以用相对较少的共享对象取代大量对象。
对象池
对象池维护一个装载空闲对象的池子,如果需要对象的时候,不是直接 new。而是转从对象池里获取。如果对象池里没有空闲对象,则创建一个新的对象,当或取出的对象完成它的职责之后,再进入池子等待下次被获取。
对象池技术的应用非常广泛,HTTP连接池和数据库连接池都是其代表应用。在 Web 前端开发中,对象池使用最多的场景时跟DOM相关的操作。很多空间和时间都消耗在了 DOM 节点上,要学会如何避免频繁地创建和删除DOM节点。
最后说一句
如果这篇文章对您有所帮助,或者有所启发的话,帮忙点赞关注一下,您的支持是我坚持写作最大的动力,多谢支持。
同系列文章
- JavaScript 设计模式之单例模式
- JavaScript 设计模式之策略模式
- JavaScript 设计模式之代理模式
- JavaScript 设计模式之迭代器模式
- JavaScript 设计模式之发布-订阅模式
- JavaScript 设计模式之命令模式
- JavaScript 设计模式之组合模式
- JavaScript 设计模式之模板方法模式
- JavaScript 设计模式之享元模式
- JavaScript 设计模式之职责链模式
- JavaScript 设计模式之中介者模式
- JavaScript 设计模式之装饰者模式
- JavaScript 设计模式之状态模式
- JavaScript 设计模式之适配器模式