享元模式适合哪些应用场景?
简化版
享元模式适合大量对象具有重复状态、并且重复状态可以共享的场景。常见应用包括文本编辑器字符对象、游戏地图元素、围棋棋子、图标和字体样式、连接或对象池、权限或配置模板等。
详细版
享元模式适用场景通常具备几个特征:
- 对象数量非常大。
- 对象创建和内存占用成为问题。
- 对象中有可重复、可抽离的内部状态。
- 外部状态可以由客户端维护。
典型例子:
- 文本编辑器中大量字符共享字体样式。
- 游戏中大量树木、子弹、粒子共享模型和贴图。
- 棋盘游戏中黑白棋子共享颜色和渲染样式。
- 图标系统中共享同一类图标元数据。
- 业务系统中共享只读配置、规则模板、权限模板。
完整版教学
一、文本编辑器字符和样式
文本编辑器可能有成千上万个字符。如果每个字符对象都保存完整字体、字号、颜色、样式,会产生大量重复。
可以把字体样式做成享元:
- 内部状态:字体名、字号、颜色、粗体等。
- 外部状态:字符内容、行列位置、选中状态。
多个字符可以共享同一个字体样式对象。
二、游戏地图和渲染对象
游戏地图上经常有大量重复元素:
- 树木。
- 草丛。
- 石头。
- 子弹。
- 粒子。
这些对象的模型、贴图、基础颜色可能相同,坐标、速度、生命值不同。
享元模式可以共享模型和贴图,把坐标等变化信息放在实例数据中。这样既减少内存,也减少资源加载次数。
三、棋类游戏中的棋子
围棋、五子棋、象棋都可以用享元思想。
例如围棋棋子只有黑白两种颜色。棋盘上每一步落子位置不同,但棋子颜色和渲染样式可以共享。
这种例子适合说明内部状态和外部状态的区别。
四、图标、字体和样式库
前端或桌面应用中,大量组件可能使用相同图标、字体、颜色配置。
这些资源对象可以集中管理:
- 图标元数据共享。
- 字体对象共享。
- 主题样式共享。
如果每个组件都复制一份资源描述,会造成浪费,也让更新难以统一。
五、只读模板和规则配置
业务系统中也有享元场景。例如:
- 通知模板。
- 权限模板。
- 风控规则模板。
- 报表字段配置。
这些模板通常是只读的,可以被多个用户、订单或请求共享。请求中的用户 ID、订单 ID、上下文参数属于外部状态。
六、对象池是否一定是享元
连接池、线程池、对象池经常和享元一起被提到,但它们不完全一样。
对象池主要复用昂贵对象,关注创建和销毁成本;享元主要共享内部状态,关注大量细粒度对象的内存占用。
如果对象池中的对象有独立生命周期和可变状态,就不能简单叫享元。它们只是都体现了复用思想。
七、不适合使用的场景
不适合享元的情况包括:
- 对象数量不多。
- 对象之间没有明显重复状态。
- 对象大部分状态都依赖上下文。
- 共享对象需要频繁修改。
- 缓存 key 种类无限增长。
这些情况下,引入享元可能得不偿失。
八、常见误区与追问
适用场景必须同时满足对象多、内部状态重复、外部状态可拆。地图上有100000棵树但纹理只有8种,字形布局有百万字符但字体组合有限,都是典型候选;若每个对象纹理都不同,K 接近 N,共享收益迅速消失。数据库连接池虽复用对象,但主要解决昂贵资源生命周期,不属于典型状态拆分享元。
| 检查维度 | 判定依据 |
|---|---|
| 高匹配 | N 大、K 小、内部状态重且不可变 |
| 低匹配 | 状态高度唯一或必须频繁修改 |
适合度 ∝ 对象数 × 重复率 × 内部状态大小
心法:先看堆里是不是有“大量长得一样的重对象”。
- 误区:对象池都属于享元模式。 对象池通常借出可变资源并归还,享元允许并发共享不可变状态。
- 追问:游戏粒子为何常用享元? 纹理和模型可共享,位置、速度和寿命由每个粒子保存。
- 误区:缓存查询结果天然适合享元。 结果缓存不一定拆分内外状态,其目标和失效语义也不同。
- 追问:如何发现候选对象? 堆转储中查看数量大、浅大小相似且字段高度重复的类型。
- 追问:什么时候应放弃? 共享键高基数、状态可变或外部参数使 API 难以维护时。
九、加强记忆
享元模式适合“数量多、重复多、可共享、可拆状态”的场景。只要对象的大部分差异来自外部上下文,而核心类型信息可以复用,就可以考虑享元。