第十二章 享元模式

享元模式(f yweight)是一种用于性能优化的模式,fy 在这里是苍蝇的意思,意为蝇量级。享元模式的核心是运用共享技术来有效支持大量细粒度的对象。

如果系统中因为创建了大量类似的对象而导致内存占用过高,享元模式就非常有用了。在 Javascrip 中,浏览器特别是移动端的内存并不算多,如何节省内存就成了一件有意义的事情。

12.1 初识享元模式

假设有个内衣工厂,目前的产品有 50 种男式内衣和 50 种女士内衣,为了推销产品,工厂决定生产一些塑料模特来穿上他们的内衣拍成广告照片。正常情况下需要 50 个男模特和 50 个女模特,然后让他们每人分别穿上一件内衣来拍照。

代码如下:

js
1class Model {
2  constructor(sex, underwear) {
3    this.sex = sex
4    this.underwear = underwear
5  }
6  takePhoto() {
7    console.log(`sex:${this.sex}-underwear:${this.underwear}`)
8  }
9}
10
11for (let i = 0; i < 50; i++) {
12  const man = new Model('man', i)
13  man.takePhoto()
14}
15
16for (let i = 0; i < 50; i++) {
17  const woman = new Model('woman', i)
18  woman.takePhoto()
19}

可以看到,我们一共会创造 100 个模特。如果将来要生产 10000 种内衣,那就需要 10000 个模特,这显然不合理。

实际上我们只需要 1 个男模特和 1 个女模特就可以。现在我们改写一下代码:

我们没必要一开始就让模特穿上工厂的衣服,只需要区分性别就可以了。所以首先改写构造 Model 的类

js
1class Model {
2  constructor(sex) {
3    this.sex = sex
4    this.underwear = null
5  }
6  takePhoto() {
7    console.log(`sex:${this.sex}-underwear:${this.underwear}`)
8  }
9}

现在我们只需要两个模特

js
1const man = new Model('man')
2const woman = new Model('woman')

然后让这两个模特不断的换衣服-拍照…

js
1for (let i = 0; i < 50; i++) {
2  man.underwear = i
3  man.takePhoto()
4}
5
6for (let i = 0; i < 50; i++) {
7  woman.underwear = i
8  woman.takePhoto()
9}

改进代码后,只需要两个对象就可以完成同样的功能。

12.2 内部状态与外部状态

享元模式要求将对象的属性划分为内部属性和外部属性。

享元模式的目标是尽量减少共享对象的数量。

关于如何划分内部和外部属性,下面有几条经验

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

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

只拥有内部状态的对象成为了共享的对象,外部状态在必要时可以被传入共享对象而组装成一个完整的对象。虽然组装对象会占用一点点时间,但是却大大减少了对象的数量。

因此,享元模式也是一种以时间换空间的优化模式。

在工厂试内衣的例子中,性别是内部状态,它可以被共享。

内衣是外部状态,它随着场景的改变而改变,它不能被共享。

通过区分这两种状态大大减少了模特的要求数量。

通常来讲,内部状态有多少种组合,就有多少个对象。因为性别只有男女,所以上面例子中会创建只会两个对象。

使用享元模式的关键在于如何区别内部状态和外部状态。

内部状态外部状态
可以被对象共享不能被其他对象共享
一般不会变化会随着场景变化