享元模式(Flyweight Pattern)

享元模式(Flyweight Pattern)

一、概念

享元模式是一种结构型设计模式 ,核心思想是:运用共享技术有效地支持大量细粒度对象的复用,从而节省内存。

「享元」= 共享 + 元素

它将对象的状态分为两部分:

状态类型 说明 是否共享
内部状态(Intrinsic State) 对象可共享的、不随环境变化的部分 ✅ 共享
外部状态(Extrinsic State) 随环境变化的、不可共享的部分 ❌ 不共享,由客户端传入

二、结构

复制代码
┌─────────────┐
│   Client     │
└──────┬──────┘
       │
       ▼
┌──────────────────┐       ┌──────────────────┐
│  FlyweightFactory │──────▶│    Flyweight      │  (接口/抽象类)
│  (享元工厂)       │       │  + operation(外部) │
└──────────────────┘       └────────┬─────────┘
       缓存池(Map)                   │
                          ┌─────────┴─────────┐
                          │                     │
                ┌─────────────────┐   ┌──────────────────────┐
                │ ConcreteFlyweight│   │ UnsharedFlyweight     │
                │ (共享的享元对象) │   │ (不共享的享元对象,可选)│
                └─────────────────┘   └──────────────────────┘

三、生活类比 🌳

想象一个围棋游戏:

  • 棋盘上可能有上百颗棋子
  • 但棋子只有 黑 和 白 两种颜色(内部状态)→ 只需要创建 2 个对象
  • 每颗棋子的位置(x, y) 不同(外部状态)→ 使用时传入

四、代码示例(围棋棋子)

1. 享元接口

java 复制代码
// 享元接口
public interface ChessPiece {
    // 外部状态通过参数传入
    void draw(int x, int y);
}

2. 具体享元类

java 复制代码
// 具体享元 ------ 共享的棋子对象
public class ConcreteChessPiece implements ChessPiece {
    
    // 内部状态:颜色(被共享,不可变)
    private final String color;
    
    public ConcreteChessPiece(String color) {
        this.color = color;
        System.out.println(">>> 创建了一个 [" + color + "] 棋子对象");
    }
    
    @Override
    public void draw(int x, int y) {
        // 外部状态:位置(由客户端传入,不共享)
        System.out.println(color + "棋子落在 (" + x + ", " + y + ")");
    }
}

3. 享元工厂

java 复制代码
import java.util.HashMap;
import java.util.Map;

public class ChessPieceFactory {
    
    // 缓存池:存储已创建的享元对象
    private static final Map<String, ChessPiece> cache = new HashMap<>();
    
    public static ChessPiece getChessPiece(String color) {
        // 如果缓存中已有,则直接返回(共享)
        if (!cache.containsKey(color)) {
            cache.put(color, new ConcreteChessPiece(color));
        }
        return cache.get(color);
    }
    
    public static int getCacheSize() {
        return cache.size();
    }
}

4. 客户端使用

java 复制代码
public class Client {
    public static void main(String[] args) {
        // 下了很多棋子,但实际只创建了 2 个对象
        
        ChessPiece black1 = ChessPieceFactory.getChessPiece("黑");
        black1.draw(3, 4);
        
        ChessPiece white1 = ChessPieceFactory.getChessPiece("白");
        white1.draw(5, 6);
        
        ChessPiece black2 = ChessPieceFactory.getChessPiece("黑");
        black2.draw(7, 8);
        
        ChessPiece white2 = ChessPieceFactory.getChessPiece("白");
        white2.draw(1, 2);
        
        ChessPiece black3 = ChessPieceFactory.getChessPiece("黑");
        black3.draw(9, 10);
        
        // 验证共享
        System.out.println("\n--- 验证 ---");
        System.out.println("black1 == black2 ? " + (black1 == black2));  // true
        System.out.println("white1 == white2 ? " + (white1 == white2));  // true
        System.out.println("实际创建对象数: " + ChessPieceFactory.getCacheSize()); // 2
    }
}

5. 运行输出

java 复制代码
>>> 创建了一个 [黑] 棋子对象
黑棋子落在 (3, 4)
>>> 创建了一个 [白] 棋子对象
白棋子落在 (5, 6)
黑棋子落在 (7, 8)
白棋子落在 (1, 2)
黑棋子落在 (9, 10)

--- 验证 ---
black1 == black2 ? true
white1 == white2 ? true
实际创建对象数: 2

🎯 下了 5 颗棋子,但只创建了 2 个对象! 这就是享元模式的威力。


五、现实中的应用场景

场景 内部状态(共享) 外部状态(不共享)
Java String 常量池 字符串值 引用变量
Java Integer 缓存(-128~127) 数值 使用位置
游戏中的粒子系统 粒子纹理、颜色 位置、速度
文本编辑器的字符渲染 字符字体样式对象 字符在文档中的位置
数据库连接池 连接对象 使用者、使用时间

六、优缺点

✅ 优点

  • 大幅减少内存使用,避免创建大量相似对象
  • 提高系统性能

❌ 缺点

  • 增加系统复杂度:需要分离内部状态和外部状态
  • 享元对象的内部状态必须不可变(线程安全)
  • 读取外部状态会使运行时间略微增加(空间换时间的逆向,这里是时间换空间)

七、一句话总结

享元模式 = 缓存池 + 对象复用。 把不变的部分抽出来共享,把变化的部分作为参数传入,从而用少量对象表示大量实例。

相关推荐
Pniubi1 小时前
HashMap的扩容和树化边界
java·开发语言
Mikko71 小时前
用 Java 复刻了高中时在塞班上玩的「熔岩矿车」:纯 Swing、零依赖、没有一张图片,一个 jar 双击就能玩
java·游戏开发·swing
知了学历孙老师1 小时前
小自考的校考和统考,出题方和难度差在哪
java·大数据·前端
Java爱好狂.1 小时前
Java初学者如何一周速通SpringBoot!
java·spring boot·后端·spring·高并发·架构师·java面试
秋饼2 小时前
LLM API密钥动态轮换实战:Spring Vault + Spring AI + 多Provider路由
java·ai·技术分享·后端开发
gCode Teacher 格码致知2 小时前
Pandas教学-17:Series与Dataframe的关系
开发语言·python·pandas
发量惊人的中年网工3 小时前
分公司访问总部ERP慢怎么办?SD-WAN组网的排查与验收要点
服务器·开发语言·php
下页、再停留3 小时前
【C#桌面客户端系列学习-15】生成可视化图表之折线图、柱状图、饼状图
开发语言·c#
ShineWinsu3 小时前
对于JavaScript:操作元素的解析
开发语言·前端·javascript·面试·web·dom·元素
福兮说4 小时前
JSON 格式正确,数据就可靠吗?我用一份自编配置检查类型与空值
java·前端·json