第十二章 享元模式
享元模式(f yweight)是一种用于性能优化的模式,fy 在这里是苍蝇的意思,意为蝇量级。享元模式的核心是运用共享技术来有效支持大量细粒度的对象。
如果系统中因为创建了大量类似的对象而导致内存占用过高,享元模式就非常有用了。在 Javascrip 中,浏览器特别是移动端的内存并不算多,如何节省内存就成了一件有意义的事情。
12.1 初识享元模式
假设有个内衣工厂,目前的产品有 50 种男式内衣和 50 种女士内衣,为了推销产品,工厂决定生产一些塑料模特来穿上他们的内衣拍成广告照片。正常情况下需要 50 个男模特和 50 个女模特,然后让他们每人分别穿上一件内衣来拍照。
代码如下:
js1class 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 的类
js1class 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}
现在我们只需要两个模特
js1const man = new Model('man') 2const woman = new Model('woman')
然后让这两个模特不断的换衣服-拍照…
js1for (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 内部状态与外部状态
享元模式要求将对象的属性划分为内部属性和外部属性。
享元模式的目标是尽量减少共享对象的数量。
关于如何划分内部和外部属性,下面有几条经验
- 内部状态存储与对象内部
- 内部状态可以被一些对象共享
- 内部状态独立于具体的场景,通常不会改变
- 外部状态取决于场景,随着场景而变化,外部状态不能被共享
所有内部状态相同的对象都可以被指定为同一个共享的对象。而外部状态就从对象身上剥离出来,储存在外部。
只拥有内部状态的对象成为了共享的对象,外部状态在必要时可以被传入共享对象而组装成一个完整的对象。虽然组装对象会占用一点点时间,但是却大大减少了对象的数量。
因此,享元模式也是一种以时间换空间的优化模式。
在工厂试内衣的例子中,性别是内部状态,它可以被共享。
内衣是外部状态,它随着场景的改变而改变,它不能被共享。
通过区分这两种状态大大减少了模特的要求数量。
通常来讲,内部状态有多少种组合,就有多少个对象。因为性别只有男女,所以上面例子中会创建只会两个对象。
使用享元模式的关键在于如何区别内部状态和外部状态。
| 内部状态 | 外部状态 |
|---|---|
| 可以被对象共享 | 不能被其他对象共享 |
| 一般不会变化 | 会随着场景变化 |