Skip to content

享元模式(Flyweight Pattern)


1. 官方定义

"Use sharing to support large numbers of fine-grained objects efficiently."
—— 《Design Patterns: Elements of Reusable Object-Oriented Software》(GoF, 1994)

核心:通过共享技术高效支持大量细粒度对象,减少内存占用。


2. 模式解释

核心思想

  • 状态分离:将对象状态分为内部状态(可共享,如字符编码)和外部状态(不可共享,如位置坐标)。
  • 对象池化:复用已存在的同类对象(如游戏中重复的树木、子弹模型)。
  • 轻量化:减少因大量重复对象导致的资源消耗(内存、GC 压力)。

典型结构

plaintext
          Client → FlyweightFactory

                        | manages
                        |
                |<<Flyweight>>|

                        | stores intrinsic state
                ┌───────┴───────┐
                |               |
          ConcreteFlyweight  UnsharedFlyweight

优点

  • 内存优化:显著减少对象数量(如 100 万字符仅需 26 个字母对象)。
  • 性能提升:降低垃圾回收频率和初始化开销。
  • 集中管理:享元工厂统一控制对象生命周期。

缺点

  • 复杂度增加:需严格区分内部/外部状态,增加代码维护成本。
  • 线程安全风险:共享对象需处理多线程访问(如连接池中的连接对象)。

3. 解决的问题

经典场景

  1. 文本编辑器
    • 字符对象共享(每个字母仅存一份,位置/字体作为外部状态)。
  2. 游戏开发
    • 大量重复游戏实体(如《Minecraft》中的方块渲染)。
  3. 图形绘制
    • 重复图形元素(如 SVG 中相同图标的多处复用)。

4. 实现注意事项

关键实现点

  1. 状态分离设计
    • 内部状态必须完全独立于使用场景(如字符编码与颜色无关)。
  2. 享元工厂管理
    • 使用工厂类缓存和复用对象(如 FlyweightFactory.getCharacter('A'))。
  3. 不可变性保证
    • 内部状态必须不可变(否则共享会导致数据污染)。

代码示例(文本编辑器)

java
// 享元接口
interface Character {
    void draw(int x, int y, String color); // 外部状态:坐标和颜色
}

// 具体享元(内部状态:字符)
class ConcreteCharacter implements Character {
    private final char symbol; // 内部状态(不可变)
    
    public void draw(int x, int y, String color) {
        System.out.printf("Draw '%s' at (%d,%d) with %s\n", symbol, x, y, color);
    }
}

// 享元工厂
class CharacterFactory {
    private Map<Character, Character> pool = new HashMap<>();
    
    public Character getCharacter(char c) {
        if (!pool.containsKey(c)) {
            pool.put(c, new ConcreteCharacter(c));
        }
        return pool.get(c);
    }
}

// 客户端调用
CharacterFactory factory = new CharacterFactory();
Character a = factory.getCharacter('A');
a.draw(10, 20, "red"); // 外部状态由客户端传递

5. 实现变体

类型特点适用场景
单纯享元所有对象均可共享(仅内部状态)如字符、图标等不可变对象
复合享元组合多个享元对象(如单词由字符组成)需要组合结构的场景
线程局部享元为每个线程维护独立享元池(避免锁竞争)高并发环境

6. 相似模式对比

模式核心区别
单例模式单例确保全局唯一对象,享元允许多个共享对象
对象池模式对象池管理临时对象(如数据库连接),享元管理长期复用的不可变对象
原型模式通过克隆生成新对象,享元通过共享复用现有对象

7. 组合使用场景

常见搭配模式

  1. 工厂模式
    • 场景:通过享元工厂统一创建和管理共享对象。
  2. 组合模式
    • 场景:共享树形结构的叶子节点(如 UI 组件库中的重复按钮)。
  3. 状态模式
    • 场景:共享状态对象(如游戏角色的行走/奔跑状态)。

总结
享元模式是大规模对象复用的内存救星,其核心在于分离内部/外部状态并通过工厂集中管理共享池。在需要处理海量相似对象的系统中(如大型游戏、文档处理),它能显著降低资源消耗,但需谨慎设计状态分离策略以避免线程安全问题。Java 的 String 常量池是享元模式的经典实践。```