享元模式(Flyweight Pattern):通过共享优化大量细粒度对象
享元模式是结构型设计模式的一种,核心思想是通过共享技术有效支持大量细粒度对象,减少内存占用和对象创建开销。它将对象的状态分为 “内部状态”(可共享、不变)和 “外部状态”(不可共享、可变),通过共享内部状态实现对象复用,本质是 “分离与共享”。
享元模式的核心结构
享元模式通过四个角色实现对象的共享与管理,分工明确:
享元接口(Flyweight)
- 定义享元对象的公共接口,声明接收外部状态的方法。
- 示例:
Character接口(定义字符的显示方法,接收位置等外部状态)。
具体享元(ConcreteFlyweight)
- 实现享元接口,封装内部状态(可共享的不变数据),并通过接口接收外部状态(可变数据)。
- 示例:
ConcreteCharacter(存储字符的字形等内部状态,接收坐标等外部状态)。
非共享享元(UnsharedConcreteFlyweight)
- 不需要共享的享元对象,通常包含无法共享的外部状态,直接实例化无需缓存。
- 示例:
SpecialCharacter(特殊符号,使用频率低且状态独特,无需共享)。
享元工厂(FlyweightFactory)
- 负责创建和管理享元对象,维护享元池(缓存已创建的享元),确保相同内部状态的对象只被创建一次。
- 示例:
CharacterFactory(缓存字符对象,根据字符值返回共享实例)。
核心概念:内部状态与外部状态
享元模式的关键是对对象状态的拆分:
- 内部状态(Intrinsic State):
- 定义:对象固有的、不变的状态,可被多个对象共享。
- 存储位置:由享元对象自身持有。
- 示例:字符的 “字形”(如 ‘A’ 的形状固定,可共享)。
- 外部状态(Extrinsic State):
- 定义:对象依赖的、可变的状态,随场景变化,不可共享。
- 存储位置:由客户端持有,使用时传递给享元对象。
- 示例:字符的 “位置”(如 ‘A’ 在页面中的 x、y 坐标,每次显示可能不同)。
代码实现示例
以 “文档编辑器中的字符显示” 为例:文档中大量重复的字符(如 ‘a’、’b’)可共享其字形(内部状态),但位置(外部状态)不同,通过享元模式减少字符对象的创建。
1. 享元接口与具体享元
1 | // 1. 享元接口(字符) |
2. 享元工厂
1 | // 享元工厂:管理字符享元池 |
3. 客户端使用
1 | public class FlyweightDemo { |
输出结果
1 | 创建字符 'a' 的享元对象 |
享元模式的核心优势
- 减少内存占用
通过共享内部状态,避免大量重复对象的创建(如文档中重复的字符),大幅降低内存消耗。 - 提高性能
减少对象创建和垃圾回收的开销,尤其适合需要频繁创建细粒度对象的场景(如游戏中的粒子效果、字体渲染)。 - 分离可变与不可变状态
清晰区分内部状态(不变)和外部状态(可变),使系统设计更灵活,便于维护。
适用场景
- 存在大量细粒度对象
当系统中存在大量相似对象(如文本中的字符、棋盘上的棋子),且这些对象的大部分状态可共享时。 - 对象的大部分状态可共享
内部状态(不变)占比高,外部状态(可变)可通过参数传递,如:- 游戏:士兵、子弹等重复单位(共享模型、纹理,外部状态为位置、血量)。
- 图形界面:按钮、图标等组件(共享样式,外部状态为位置、大小)。
- 数据库连接池:连接对象共享连接信息(内部状态),外部状态为当前用户会话。
- 对象创建成本高
若对象初始化耗时或占用资源多(如网络连接、大型模型),通过享元池复用可显著提升性能。
优缺点分析
优点
- 资源优化:减少对象数量,降低内存和 CPU 消耗。
- 灵活性:外部状态可动态调整,不影响内部状态的共享。
- 可管理性:通过工厂统一管理享元对象,便于监控和控制。
缺点
- 复杂度增加:需要拆分内部 / 外部状态,设计享元工厂,增加了系统理解难度。
- 外部状态依赖:客户端需管理外部状态并传递给享元,若外部状态复杂,可能影响效率。
- 线程安全风险:若外部状态管理不当(如共享外部状态),可能导致线程安全问题。
经典应用案例
- 字符串常量池(String Pool)
Java 中String的intern()方法通过享元模式实现:相同字符序列的字符串共享同一个对象(如"abc" == "abc"为true)。 - 数据库连接池
连接池缓存数据库连接对象(内部状态:URL、用户名、密码),外部状态为当前会话,避免频繁创建连接。 - 游戏中的粒子系统
大量粒子(如火焰、雨滴)共享纹理和运动规则(内部状态),位置和生命周期为外部状态,通过享元复用粒子对象。 - 字体渲染引擎
相同字符(如 ‘A’)共享字形数据(内部状态),在不同位置显示时传递坐标(外部状态)。
总结
享元模式通过拆分对象的内部状态(共享)和外部状态(可变),实现了大量细粒度对象的高效复用,核心是 “共享不变,传递可变”。它特别适合存在大量相似对象的场景,能显著优化内存和性能。但需注意平衡设计复杂度,避免过度拆分状态导致系统难以维护

v1.3.10