12.设计模式-FLYWEIGHT(享元)

一、模式定义与核心价值

享元模式是一种结构型设计模式,其核心目标是通过共享技术减少内存消耗,有效支持大量细粒度对象的复用。该模式通过分离对象的内部状态(可共享)与外部状态(不可共享),实现以下核心价值:

  1. 内存优化:将重复数据集中存储,避免创建重复对象(如文档编辑器中的字符对象)
  2. 性能提升:减少对象创建/销毁开销,降低GC频率(如游戏场景的粒子特效管理)
  3. 状态解耦:将可变状态外置,保证共享对象的安全性与独立性(如数据库连接池的参数管理)

工业级场景

  • 文本处理系统(每个字符的字体信息共享)
  • 游戏开发(树木/建筑等重复元素的渲染优化)
  • UI框架(按钮/图标等控件的复用)
  • 数据库连接池(复用TCP连接对象)

二、模式组成与UML类图

核心角色
  1. Flyweight(抽象享元)
    • 定义共享对象的接口(如Character类的display方法)
  1. ConcreteFlyweight(具体享元)
    • 实现抽象接口并存储内部状态(如字符的Unicode编码)1****6
  1. FlyweightFactory(享元工厂)
    • 管理共享对象池,实现对象复用逻辑(哈希表存储已创建对象)3****9
  1. 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

协作流程

  1. 客户端请求对象时,工厂先检查对象池是否存在
  2. 若存在则直接返回,否则创建新对象并加入池中
  3. 客户端调用时传递外部状态(如字符位置坐标)

三、代码实现示例

场景:实现游戏子弹对象的共享(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纹理子弹

四、工业级源码应用

  1. Java字符串常量池
    • JVM通过String.intern()方法实现字符串共享,相同字面量的String对象指向同一内存地址
String s1 = "hello";
String s2 = new String("hello").intern();
System.out.println(s1 == s2); // 输出true
  1. JDBC连接池
    • DataSource实现类通过享元模式复用连接对象,避免频繁创建/关闭TCP连接
  1. Unity游戏引擎
    • 粒子系统通过共享材质(Material)对象,数万粒子实例共用同一纹理资源
  1. Android UI框架
    • RecyclerView的视图缓存机制本质是享元模式,复用滚动出屏幕的Item视图
  1. 日志框架优化
    • Logback通过LoggerCache共享Logger实例,避免为每个类重复创建日志对象

五、模式优劣与选型建议

优势

  • 内存节省效果显著(1万个对象可压缩至数十个共享实例)
  • 提升GC效率(减少短生命周期对象)
  • 符合开闭原则(新增共享类型无需修改工厂)

劣势

  • 增加系统复杂度(需严格区分内外状态)
  • 线程安全风险(共享对象需同步控制)
  • 过度使用可能导致池膨胀(需设定缓存淘汰策略)

最佳实践

  1. 识别高频重复对象(如配置文件解析结果)
  2. 结合对象池技术(如Apache Commons Pool)
  3. 监控池容量(防止内存泄漏)

总结

享元模式如同软件系统的“资源管家”,通过对象复用状态分离两大核心策略,在Java生态、游戏引擎等场景中展现出强大的优化能力。其设计精髓在于平衡内存与性能,开发者需重点把控共享粒度与线程安全,让这一经典模式真正成为高并发、大数据量场景的利器。

posted @ 2025-04-12 10:47  雾里看花的少年  阅读(36)  评论(0)    收藏  举报