享元模式

概述

考虑一个文档,它有大量字符构成。如果对所有字符构建一个对象,那就会造成创建的对象过于繁多,从而导致内存上的消耗。《设计模式:可复用面向软件的基础》使用文档的物理结构对其进行建模,首先一个页面由 Column 列对象构成,Column 由 Row 行对象构成,Row 由字符对象构成。字符对象使用享元模式,即从 Flyweight Pool 中获取享元对象,再由 Row 对象将字符的位置等相关信息注入到享元对象中。位置等相关信息称为外部状态,不可共享,由场景(即享元对象所在的环境)决定并注入到享元对象中;可共享的信息存在享元对象内部,称为内部状态。在文档中,字符的内部状态即 a、b 等不同字符内容,因此享元对象的种数取决于字符类型、样色等内部状态。

基于上述,Flyweight 享元模式指通过基于类型建模,以节省大数量级对象逐个建模时耗费的内存空间。Flyweight 字面上是蝇量级的意思。应用享元模式的条件点在于对象的大多数状态可以转化为外部状态,即持有不同内部状态的对象类目远小于纯粹创建对象的数目。

结构

  • FlyWeight:定义享元对象的接口,接受并处理外部状态。
  • ConcreteFlyweight:基于内部状态实现不同的享元对象。
  • UnsharedConcreteFlyweight:不可共享的 Flyweight 子类,通常将 ConcreteFlyweight 作为子类,如上文中的 Row、Column。
  • FlyweightFactory:创建并管理 FlyWeight 对象。当用户请求 FlyWeight 对象时,FlyWeightFactory 将复用已创建的 FlyWeight 实例或重新创建一个。
  • Client:维持一个对 FlyWeight 对象的引用,计算并存储一个或多个 FlyWeight 对象的外部状态。

共享模式经常和组合模式一起使用,即叶子节点使用共享模式,父节点的指针作为外部状态存为叶子节点的一部分。

可以窥见,浏览器的绘制也使用了享元模式。

实例

前述文档

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
class GlyphContext {
index;// 文档的当前位置
fonts;

next(step = 1){// 遍历文档
this.index += step;
};
insert(quantity = 1){};// 插入内容长度

setFont(font, span = 1){};// 设置字体,span 指影响多少个字符
getFont(){};
}

class Glyph {// 子类如 Row, Column
draw(window, glyphContext){};

setFont(font, glyphContext){};
getFont(glyphContext){};

first(glyphContext){};
next(glyphContext){};
isDone(glyphContext){};
current(glyphContext){};

insert(glyph, glyphContext){};
remove(glyphContext){};
}

class Row extends Glyph {
draw(window, glyphContext){};
}

class Column extends Glyph {
draw(window, glyphContext){};
}

class Character extends Glyph {
charcode;
constructor(char){
this.charcode = char;
}
draw(window, glyphContext){};
}

class GlyphFactory {
characters = new Map();// 存储字符表
createCharacter(char){
if (this.characters[char]){
return this.characters[char];
};

this.characters[char] = new Character(char);
return this.characters[char];
};
createRow(){
return new Row();
};
createColumn(){
return new Column();
};
}

对象池

对象池、http 连接池、数据库连接池均可使用乡愿模式实现。react 源码中也经常使用对象池技术。以下是通用的对象池实现代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const createPoolFactory = createFactory => {
const pool = [];

return {
create(...args){// 创建元素
if (pool.length){
return pool.shift();
}
const item = createFactory(...args);
return item;
},
recover(item){// 将元素回收到对象池中
pool.push(item);
}
}
}