12.设计模式-FLYWEIGHT(享元)
一、模式定义与核心价值
享元模式是一种结构型设计模式,其核心目标是通过共享技术减少内存消耗,有效支持大量细粒度对象的复用。该模式通过分离对象的内部状态(可共享)与外部状态(不可共享),实现以下核心价值:
- 内存优化:将重复数据集中存储,避免创建重复对象(如文档编辑器中的字符对象)
- 性能提升:减少对象创建/销毁开销,降低GC频率(如游戏场景的粒子特效管理)
- 状态解耦:将可变状态外置,保证共享对象的安全性与独立性(如数据库连接池的参数管理)
工业级场景:
- 文本处理系统(每个字符的字体信息共享)
- 游戏开发(树木/建筑等重复元素的渲染优化)
- UI框架(按钮/图标等控件的复用)
- 数据库连接池(复用TCP连接对象)
二、模式组成与UML类图
核心角色
- Flyweight(抽象享元)
-
- 定义共享对象的接口(如
Character
类的display
方法)
- 定义共享对象的接口(如
- ConcreteFlyweight(具体享元)
-
- 实现抽象接口并存储内部状态(如字符的Unicode编码)1****6
- FlyweightFactory(享元工厂)
-
- 管理共享对象池,实现对象复用逻辑(哈希表存储已创建对象)3****9
- Client(客户端)
-
- 维护外部状态并通过工厂获取享元对象
UML类图
classDiagram
class Flyweight {
<<interface>>
+operation(extrinsicState)
}
class ConcreteFlyweight {
-intrinsicState
+operation(extrinsicState)
}
class FlyweightFactory {
-pool: Map
+getFlyweight(key)
}
class Client {
-extrinsicState
}
Flyweight <|.. ConcreteFlyweight
FlyweightFactory --> Flyweight
Client --> FlyweightFactory
Client --> Flyweight
协作流程:
- 客户端请求对象时,工厂先检查对象池是否存在
- 若存在则直接返回,否则创建新对象并加入池中
- 客户端调用时传递外部状态(如字符位置坐标)
三、代码实现示例
场景:实现游戏子弹对象的共享(1000发子弹复用5种纹理)
1. 抽象享元与具体实现
// 子弹纹理接口
interface BulletTexture {
void render(int x, int y, String color);
}
// 具体纹理类(内部状态:纹理ID)
class ConcreteBulletTexture implements BulletTexture {
private final String textureId;
public ConcreteBulletTexture(String id) {
this.textureId = id;
}
@Override
public void render(int x, int y, String color) {
System.out.printf("在(%d,%d)渲染%s色%s纹理子弹\n",
x, y, color, textureId);
}
}
2. 享元工厂
class BulletTextureFactory {
private static final Map<String, BulletTexture> pool = new HashMap<>();
public static BulletTexture getTexture(String id) {
return pool.computeIfAbsent(id, k -> new ConcreteBulletTexture(k));
}
}
3. 客户端调用
public class GameClient {
public static void main(String[] args) {
// 获取共享纹理对象
BulletTexture textureA = BulletTextureFactory.getTexture("T001");
BulletTexture textureB = BulletTextureFactory.getTexture("T002");
// 模拟子弹发射(外部状态:位置+颜色)
textureA.render(100, 200, "红");
textureB.render(150, 300, "蓝");
// 复用纹理A
BulletTextureFactory.getTexture("T001").render(200, 400, "金");
}
}
输出结果:
在(100,200)渲染红色T001纹理子弹
在(150,300)渲染蓝色T002纹理子弹
在(200,400)渲染金色T001纹理子弹
四、工业级源码应用
- Java字符串常量池
-
- JVM通过
String.intern()
方法实现字符串共享,相同字面量的String
对象指向同一内存地址
- JVM通过
String s1 = "hello";
String s2 = new String("hello").intern();
System.out.println(s1 == s2); // 输出true
- JDBC连接池
-
DataSource
实现类通过享元模式复用连接对象,避免频繁创建/关闭TCP连接
- Unity游戏引擎
-
- 粒子系统通过共享材质(Material)对象,数万粒子实例共用同一纹理资源
- Android UI框架
-
RecyclerView
的视图缓存机制本质是享元模式,复用滚动出屏幕的Item视图
- 日志框架优化
-
- Logback通过
LoggerCache
共享Logger实例,避免为每个类重复创建日志对象
- Logback通过
五、模式优劣与选型建议
优势:
- 内存节省效果显著(1万个对象可压缩至数十个共享实例)
- 提升GC效率(减少短生命周期对象)
- 符合开闭原则(新增共享类型无需修改工厂)
劣势:
- 增加系统复杂度(需严格区分内外状态)
- 线程安全风险(共享对象需同步控制)
- 过度使用可能导致池膨胀(需设定缓存淘汰策略)
最佳实践:
- 识别高频重复对象(如配置文件解析结果)
- 结合对象池技术(如Apache Commons Pool)
- 监控池容量(防止内存泄漏)
总结
享元模式如同软件系统的“资源管家”,通过对象复用与状态分离两大核心策略,在Java生态、游戏引擎等场景中展现出强大的优化能力。其设计精髓在于平衡内存与性能,开发者需重点把控共享粒度与线程安全,让这一经典模式真正成为高并发、大数据量场景的利器。