共享不变的部分
享元模式把对象状态拆成两份:可共享且不变的内部状态集中成少量实例,随调用变化的外部状态由使用方在使用时传入。这样数万个「对象」在内存里只对应几十个共享实例,构造与内存开销显著下降。它的前提是对象数量大,且大部分状态确实可以复用。
基本信息
| 意图 | 共享内部状态、分离外部状态 |
|---|---|
| 前提 | 对象数量大且大部分状态可复用 |
| 关键 | 被共享的实例必须不可变 |
| 配套 | 工厂管理共享池,加弱引用或容量上限控制 |
典型使用场景
- 文本编辑器:字符的字体与字号属性共享,位置与颜色逐次传入
- 地图与游戏:瓦片、植被、粒子按类型共享几何与贴图,坐标作为外部状态
- 大规模渲染:同一树种共享模型,坐标与缩放外部传入
- 对象池:数据库连接、线程等昂贵对象的复用
- 字符串驻留:运行时把相同字面量指向同一实例,思路与此一致
形态示意
形态示意
# 内部状态:可共享,创建后不再变
class TreeType:
def __init__(self, name, texture): # 种类与贴图只建一份
self.name = name
self.texture = texture
def draw(self, canvas, x, y): # 外部状态每次传入
canvas.paint(self.texture, x, y)
# 森林里几万棵树共享少量 TreeType 实例,只有坐标各自持有
forest = [Tree(tree_types['oak'], x, y) for x, y in coords]内部状态与外部状态的差别
| 维度 | 内部状态 | 外部状态 |
|---|---|---|
| 是否可变 | 不可变 | 随调用变化 |
| 存放位置 | 共享实例,由工厂持有 | 调用方或上下文对象 |
| 典型例子 | 字体、贴图、瓦片数据 | 坐标、颜色、时间戳 |
| 误判后果 | 共享可变数据互相串改 | 为共享而复制大量状态,收益归零 |
共享池要设上限或使用弱引用:只增不减的池最终会变成内存泄漏;用弱引用可以让不再被使用的共享实例被回收,代价是同一类型可能被重建多次。
先度量再动手:用堆直方图、对象计数或内存分析确认对象数量与占用确实是瓶颈,再引入享元。否则多出来的间接层只会增加复杂度,收益无法验证。