『面试的底气』—— 设计模式之享元模式

1,215 阅读3分钟

什么是享元模式

享元模式是一种用于性能优化的模式,享元模式的核心是运用共享技术来有效支持大量细粒度的对象。

如果系统中因为创建了大量类似的对象而导致内存占用过高,享元模式就非常有用了。

享元模式要求将对象的属性划分为内部属性与外部属性。享元模式的目标是尽量减少共享对象的数量,关于如何划分内部状态和外部状态,有以下几点建议:

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

把所有内部属性相同的对象都指定为同一个共享的对象。而外部属性可以从对象身上剥离出来,并储存在外部。剥离了外部属性的对象成为共享对象,外部属性在必要时被传入共享对象来组装成一个完整的对象。虽然组装外部属性成为一个完整对象的过程需要花费一定的时间,但却可以大大减少系统中的对象数量,相比之下,这点时间或许是微不足道的。因此,享元模式是一种用时间换空间的优化模式。

实现一个享元模式

假设有个服装厂,目前的产品有 50 种男式外套和 50 种女士外套,为了推销产品,工厂决定生产一些塑料模特来穿上他们的外套拍成广告照片。 正常情况下需要 50 个男模特和 50 个女模特,然后让他们每人分别穿上一件外套来拍照。不使用享元模式的情况下,在程序里也许会这样实现:

class Model {
  constructor(sex, looseCoat) {
    this.sex = sex;
    this.looseCoat = looseCoat;
  }
  takePhoto() {
    console.log('sex= ' + this.sex + ' looseCoat=' + this.looseCoat);
  }
}
for (let i = 1; i <= 50; i++) {
  const maleModel = new Model('male', 'looseCoat' + i);
  maleModel.takePhoto();
};
for (let j = 1; j <= 50; j++) {
  const femaleModel = new Model('female', 'looseCoat' + j);
  femaleModel.takePhoto();
};

按照上面的程序运行,那50种男式外套和50种女生外套,将会产生100个模特。假如有1万种男式外套和1万种女生外套,那不是需要1万个模特,那这个程序可能会因为存在如此多的对象(模特)已经提前崩溃。然而现实中不会有这么傻的工厂。只需要1个男模特和1个女模特。

那么程序应该这么优化。

Model只需要只接收sex参数,只需要区别男女模特即可。

class Model {
  constructor(sex) {
    this.sex = sex;
  }
  takePhoto() {
    console.log('sex= ' + this.sex + ' looseCoat=' + this.looseCoat);
  }
}

分别创建一个男模特对象和一个女模特对象:

const maleModel = new Model('male');
const femaleModel = new Model('female');

给男模特依次穿上所有的男装,并进行拍照:

for (let i = 1; i <= 50; i++) {
  maleModel.looseCoat = 'looseCoat' + i;
  maleModel.takePhoto();
};

同样,给女模特依次穿上所有的女装,并进行拍照:

for (var j = 1; j <= 50; j++) {
  femaleModel.looseCoat = 'looseCoat' + j;
  femaleModel.takePhoto();
};

以上就是一个非常经典的享元模式。

把模特的性别sex当作模特这个对象的内部属性,把模特身上的外套looseCoat当作外部属性。把外套looseCoat属性剥离后,模特这个对象就可以被共享,然后再必要的时候给模特附上外套looseCoat属性,组成一个完整的对象。

什么时候使用享元模式

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

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