JavaScript 设计模式之享元模式

536 阅读5分钟

这是我参与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();
};

要得到一张照片,每次都需要传入 sexcloth 参数,如上所述,会产生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();
};

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

内部状态与外部状态

享元模式要求将对象的属性划分为内部状态与外部状态,享元模式的目标是尽量减少共享对象的数量。

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

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

享元模式的通用结构

在上述的示例中,还存在以下两个问题:

  1. 通过构造函数显式地 new 出了男女两个 model 对象,在其他系统中,也许并不是一开始就需要所有的共享对象。
  2. model 对象手动设置了 cloth 外部状态,在更复杂的系统中,这不是一个最好的方式,因为外部状态可能会相当复杂,他们与共享对象的联系会变的困难。

通过一个对象工厂来解决第一个问题,只有当某种共享对象被真正需要时,它才从工厂中被创建出来。对于第二个问题,可以用一个管理器来记录对象相关的外部状态,使这些外部状态通过某个钩子和共享对象联系起来。

享元模式的适用性

享元模式是一种很好的性能优化方案,但它也会带来一些复杂性的问题,一般来说,以下情况发生时便可以使用享元模式。

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

对象池

对象池维护一个装载空闲对象的池子,如果需要对象的时候,不是直接 new。而是转从对象池里获取。如果对象池里没有空闲对象,则创建一个新的对象,当或取出的对象完成它的职责之后,再进入池子等待下次被获取。 对象池技术的应用非常广泛,HTTP连接池和数据库连接池都是其代表应用。在 Web 前端开发中,对象池使用最多的场景时跟DOM相关的操作。很多空间和时间都消耗在了 DOM 节点上,要学会如何避免频繁地创建和删除DOM节点。

最后说一句

如果这篇文章对您有所帮助,或者有所启发的话,帮忙点赞关注一下,您的支持是我坚持写作最大的动力,多谢支持。

同系列文章

  1. JavaScript 设计模式之单例模式
  2. JavaScript 设计模式之策略模式
  3. JavaScript 设计模式之代理模式
  4. JavaScript 设计模式之迭代器模式
  5. JavaScript 设计模式之发布-订阅模式
  6. JavaScript 设计模式之命令模式
  7. JavaScript 设计模式之组合模式
  8. JavaScript 设计模式之模板方法模式
  9. JavaScript 设计模式之享元模式
  10. JavaScript 设计模式之职责链模式
  11. JavaScript 设计模式之中介者模式
  12. JavaScript 设计模式之装饰者模式
  13. JavaScript 设计模式之状态模式
  14. JavaScript 设计模式之适配器模式