享元模式(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) 数值 使用位置
游戏中的粒子系统 粒子纹理、颜色 位置、速度
文本编辑器的字符渲染 字符字体样式对象 字符在文档中的位置
数据库连接池 连接对象 使用者、使用时间

六、优缺点

✅ 优点

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

❌ 缺点

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

七、一句话总结

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

相关推荐
霸道流氓气质1 小时前
SpringBoot+LangChain4j+Ollama+MCP实现智能天气工具调用示例
java·spring boot·后端
这是程序猿1 小时前
设计模式入门:Java 单例模式(Singleton)详解,从入门到实战
java·单例模式·设计模式
codingPower1 小时前
ApplicationListener 和 SpringApplicationRunListener 深度解析对比
java·开发语言·spring boot
ch.ju1 小时前
Java Programming Chapter 2-Recursion of function
java·开发语言
yuanpan1 小时前
Python + matplotlib 数据可视化入门教程:折线图、柱状图、饼图与 Excel 绘图
开发语言·python·opencv
Highcharts.js1 小时前
Highcharts 助力医疗与生命科学研究的数据分析|让医学数据轻易呈现
开发语言·信息可视化·highcharts·实战代码·响应式图表
铁皮哥1 小时前
【后端开发】RabbitMQ、RocketMQ、Kafka 怎么选?我从业务场景重新梳理了一遍
java·linux·数据库·分布式·kafka·rabbitmq·rocketmq
suixinm1 小时前
Agent 设计模式:从 ReAct、CodeAct 到 Agentic Rag 与多智能体
设计模式·ai·react·rag·ai agent·agent智能体·multi-agent
AC赳赳老秦1 小时前
数据库操作自动化:用 OpenClaw 对接 Navicat/DBeaver,实现数据备份、脱敏、日常操作自动化
java·运维·数据库·python·oracle·自动化·openclaw