代码界的「分身大师」:享元模式的共享艺术

161 阅读4分钟

代码界的「分身大师」:享元模式的共享艺术


一、当内存开始「吃紧」

你是否经历过这样的内存危机?
游戏里十万个小兵同时冲锋,每个对象都背着独立的内存包袱;
文档编辑器打开万字长文,每个字符都占着独立内存豪宅;
粒子系统喷射百万火星,每个粒子都喊着「我要独立!」...

享元模式就像代码界的影分身术——「别浪费!共享内在状态,保留外在个性!」 通过将对象拆解为共享的「灵魂」和独有的「皮囊」,让百万雄师也能轻装上阵!


二、影分身的核心奥义(UML图)

          ┌─────────────┐          ┌─────────────┐  
          │   Flyweight │          │  Flyweight  │  
          │   Factory   │<>───────>│   Interface │  
          ├─────────────┤          ├─────────────┤  
          │ +getFlyweight()│       │ +operation()│  
          └──────△──────┘         └──────△──────┘  
                 │                        │  
          ┌──────┴──────┐          ┌──────┴──────┐  
          │   Client    │          │  Concrete   │  
          │             │          │  Flyweight  │  
          └─────────────┘          └─────────────┘  
  • 分身卷轴(Flyweight Factory):管理共享对象的工厂
  • 影分身术(Flyweight Interface):定义共享方法
  • 鸣人本体(Concrete Flyweight):共享的内在状态
  • 影分身(Client):持有独特外在状态的客户端

三、字符编辑器的分身大战(代码实战)

1. 定义字符灵魂(享元接口)
// 字符的灵魂(共享的字体、颜色等属性)  
interface CharacterStyle {  
    void render(char c, int x, int y); // 外在坐标由客户端传递  
}  
2. 制造灵魂容器(享元工厂)
class StyleFactory {  
    private Map<String, CharacterStyle> pool = new HashMap<>();  
    
    public CharacterStyle getStyle(String font, int color) {  
        String key = font + "#" + color;  
        if (!pool.containsKey(key)) {  
            pool.put(key, new ConcreteStyle(font, color));  
        }  
        return pool.get(key);  
    }  
}  
3. 具体灵魂实现
class ConcreteStyle implements CharacterStyle {  
    private String font;  // 共享属性  
    private int color;     // 共享属性  
    
    public ConcreteStyle(String font, int color) {  
        this.font = font;  
        this.color = color;  
    }  
    
    public void render(char c, int x, int y) {  
        System.out.printf("在(%d,%d)渲染【%c】:字体=%s,颜色=#%06X\n",  
                          x, y, c, font, color);  
    }  
}  
4. 组装字符分身
class Character {  
    private char value;      // 外在状态  
    private int x, y;       // 外在状态  
    private CharacterStyle style; // 内在状态(共享)  
    
    public Character(char c, int x, int y, CharacterStyle style) {  
        this.value = c;  
        this.x = x;  
        this.y = y;  
        this.style = style;  
    }  
    
    public void draw() {  
        style.render(value, x, y);  
    }  
}  

// 客户端使用  
public class WordProcessor {  
    public static void main(String[] args) {  
        StyleFactory factory = new StyleFactory();  
        
        List<Character> doc = new ArrayList<>();  
        doc.add(new Character('H', 0, 0, factory.getStyle("宋体", 0xFF0000)));  
        doc.add(new Character('i', 10, 0, factory.getStyle("宋体", 0xFF0000)));  
        doc.add(new Character('!', 20, 0, factory.getStyle("黑体", 0x00FF00)));  
        
        doc.forEach(Character::draw);  
    }  
}  
/* 输出:  
在(0,0)渲染【H】:字体=宋体,颜色=#FF0000  
在(10,0)渲染【i】:字体=宋体,颜色=#FF0000  
在(20,0)渲染【!】:字体=黑体,颜色=#00FF00  
*/  

四、影分身 vs 克隆人军团

维度享元模式普通对象实例化
内存占用共享内在状态(内存友好)每个对象独立占用内存
适用场景大量相似对象存在重复状态对象状态高度独立
性能需要查找共享对象(略慢)直接实例化(更快)
复杂度需要区分内外状态对象状态统一管理
现实类比共享单车每人一辆私家车

五、代码界的共享经济

  1. 游戏开发:十万树苗共享纹理和模型
  2. 文档处理:百万字符共享字体配置
  3. UI框架:按钮/图标共享主题样式
  4. 棋牌游戏:棋盘棋子共享外观资源
  5. 数据库连接池:复用连接对象

冷知识
Java的String常量池就是享元模式的实践,相同字面量共享内存!


六、防内存泄漏指南

  1. 控制对象池大小
// LRU缓存策略  
if (pool.size() > MAX_POOL_SIZE) {  
    removeOldestEntry();  
}  
  1. 软引用优化
private Map<String, SoftReference<CharacterStyle>> pool = new HashMap<>();  
// 内存不足时自动回收  
  1. 线程安全处理
public synchronized CharacterStyle getStyle(String key) {  
    // 加锁防止并发问题  
}  
  1. 避免状态污染
// 享元对象必须不可变  
public final class ConcreteStyle {  
    private final String font; // final保证不可变  
    // ...  
}  
  1. 外部状态独立性
// 确保外部状态不依赖享元对象  
class Character {  
    // 坐标等状态必须由客户端管理  
}  

七、分身术总结

享元模式让代码成为内存管理大师:

  • :用于存在大量重复状态的场景
  • :严格区分内部状态和外部状态
  • 不要:在对象状态高度独立时强行使用
  • 不要:让享元对象持有外部状态引用

当你在Java中写下"hello".intern()时,请想起享元模式——那个在JVM深处默默优化字符串存储的隐形分身师!