初探享元模式

代码链接

koonchen/design-patterns/flyweight

要点

运用共享技术有效地支持大量细粒度的对象。

有些程序得益于在其整个设计过程中采用了对象技术,但是简单化的实现代价极大。

在实现一个编辑器的时候,我们会用对象来表示嵌入的成分,例如表格和图形,我们可以想象 字符对象 被放在 行对象 里, 行对象 被放在 列对象 里。但是这种设计将导致代价太大,文档中有成千上万的字符对象,这对于内存是极大的开销。 Flyweight 模式描述了如何共享对象,以至于可以细粒度地使用对象而无需高昂的价格。

flyweight (这里指的是对象不是模式) 是一个共享对象,它是独立的,它拥有 内部状态 和 外部状态 ,内部状态存储于 flyweight 中,它包含了独立于 flyweight 场景的信息,这些信息使得 flyweight 可以被共享,而外部状态取决于 Flyweight 模式的场景,不可以被共享。

Flyweight 模式对那些数量太大而难以用对象来表示的概念进行建模。我们可以对字母表中每一个字母创建一个 flyweight ,每个 flyweight 存储一个字符代码,字符代码是内部状态,而其他信息是外部状态,比如位置。

逻辑上,我们的字符出现对应如下:

cover

使用 flyweight 对象以后,一个特定字符指向同一个实例:

cover

表示字母 a 的 flyweight 不需要知道字符具体的位置和字体,它只含有字符,用户根据具体的场景绘制自己的 flyweight , row 对象知道它的子女应该在哪儿绘制自己保证横向排列,因此 row 在绘制时可以向每一个子女传递它的位置。

Flyweight 模式的适用情况 :

  • 一个应用适用了大量的对象。
  • 完全由于大量对象,导致极大存储开销。
  • 对象的大多数状态都可以转换成外部状态。
  • 如果删除对象的外部状态,那么可以使用相对较少的共享对象取代很多对象。
  • 应用程序不依赖于对象标识,由于 Flyweight 模式下,对象可以被共享,所以只有概念有别的对象的标识将不同。

Flyweight 模式的参与者:

  • Flyweight (Glyph) — 通过这个接口 flyweight 可以接受并作用于外部状态。
  • ConcreteFlyweight (Character) — 实现了 Flyweight 接口,它的存储状态是内部的。
  • UnsharedConcreteFlyweight (Row, Column) — 并非所有 Flyweight 子类都需要被共享,这个对象通常将 ConcreteFlyweight 对象作为子节点。
  • FlyweightFactory — 创建并管理 flyweight 对象。
  • Client — 维持 flyweight 应用,保存 flyweight 的外部状态。

Flyweight 模式的结构也呼之欲出了:

cover

Flyweight 模式的协作方式如下:

  • flyweight 执行时所需的状态必定是内部的或外部的,内部状态存储于 ConcreteFlyweight 对象之中;而外部对象则由 Client 对象存储。
  • 用户不直接对 ConcreteFlyweight 类进行实例化,而只能从 FlyweightFactory 对象得到 ConcreteFlyweight 对象,这保证了他们一定程度的共享。

Flyweight 模式常常和 Composite 模式相结合组成一个层次结构,这一结构是共享叶节点。但是结果是叶子节点不能存储指向父节点的指针,而父节点的指针将传给 flyweight 作为它外部状态的一部分。

实现

在实现 Flyweight 模式时,需要注意以下几点:

  • 删除外部状态:该模式的可用性很大程度上取决于是否容易识别外部状态并将它从共享对象中删除,如果不同种类的外部状态和共享前对象数目相同的话,删除外部状态不会降低存储消耗。理想情况下,外部状态可以由一个单独的对象结构计算得到,且该结构的存储要求非常小。
  • 管理共享对象:对象是共享的,用户不能直接对它进行实例化,我们可以用 FlyweightFactory 找到特定的 flyweight 对象。这里共享的概念和单例类似。

java 实现

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
import java.util.EnumMap;
import java.util.Map;

enum FlyweightType {
SHARED, UNSHARED
}

interface Flyweight {
void operation();
}

class ConcreteFlyweight implements Flyweight {

@Override
public void operation() {
System.out.println("ConcreteFlyweight instance has been created");
}
}

class UnsharedConcreteFlyweight implements Flyweight {

@Override
public void operation() {
System.out.println("UnsharedConcreteFlyweight instance has been created");
}
}

class FlyweightFactory {
private final Map<FlyweightType, Flyweight> factory;

public FlyweightFactory() {
factory = new EnumMap(FlyweightType.class);
}

Flyweight getFlyweight(FlyweightType type) {
Flyweight flyweight = factory.get(type);
if (flyweight == null) {
switch (type) {
case SHARED:
flyweight = new ConcreteFlyweight();
factory.put(type, flyweight);
break;
case UNSHARED:
flyweight = new UnsharedConcreteFlyweight();
factory.put(type, flyweight);
break;
}
}
return flyweight;
}
}


public class FlyweightClient {
public static void main(String[] args) {
FlyweightFactory factory = new FlyweightFactory();
factory.getFlyweight(FlyweightType.SHARED).operation();
factory.getFlyweight(FlyweightType.UNSHARED).operation();
}
}

实现还是非常简单的,这里用了枚举,所以采用 EnumMap ,但它不是线程安全的。

python 实现

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
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

from enum import Enum, unique

@unique
class FlyweightType(Enum):
SHARED = 0
UNSHARED = 1

class Flyweight(object):
def operation(self):
print('Flyweight instance has been created')

class ConcreteFlyweight(Flyweight):
def operation(self):
print('ConcreteFlyweight instance has been created')

class UnsharedConcreteFlyweight(Flyweight):
def operation(self):
print('UnsharedConcreteFlyweight instance has been created')

class FlyweightFactory(object):
def __init__(self):
self._flyweight_factory = dict()
def get_flyweight(self, flyweight_type):
flyweight = self._flyweight_factory.get(flyweight_type)
if flyweight is None:
if flyweight_type == FlyweightType.SHARED:
flyweight = ConcreteFlyweight()
self._flyweight_factory[flyweight_type] = flyweight
elif flyweight_type == FlyweightType.UNSHARED:
flyweight = UnsharedConcreteFlyweight()
self._flyweight_factory[flyweight_type] = flyweight
return flyweight

if __name__ == '__main__':
factory = FlyweightFactory()
factory.get_flyweight(FlyweightType.SHARED).operation()
factory.get_flyweight(FlyweightType.UNSHARED).operation()

实现也是大同小异。

  • 我们已经知道 Flyweight 模式可以和 Composite 模式结合起来,共享叶节点。
  • 通常,最好用 Flyweight 模式实现 State 和 Strategy 对象。