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

六、优缺点

✅ 优点

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

❌ 缺点

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

七、一句话总结

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

相关推荐
IT笔记7 小时前
【Rust】Rust Match 模式匹配详解
java·开发语言·rust
逝水无殇8 小时前
C# 运算符重载详解
开发语言·后端·c#
我才是银古8 小时前
构建 Java GIS 工具库:从碎片化 API 到统一抽象的设计实践
java·gis·geotools·gdal·esri
TPBoreas8 小时前
配置信息防泄露方案:.env 环境隔离详解(dotenv-java)
java·开发语言
敲代码的嘎仔9 小时前
实习日志day6--实习日志day6--title命名规范化&businessType纠正&补充缺失的@Log注解&报警与通信模块补充&产出阶段总结文档
java·开发语言·人工智能·git·python·实习·大二
江华森9 小时前
Python 实现高德地图找房(一):环境搭建与数据爬虫
开发语言·爬虫·python
杜子不疼.9 小时前
【Qt初识】信号槽(三):机制意义、断开连接与 Lambda 表达式
开发语言·数据库·qt
编程(变成)小辣鸡9 小时前
Spring事务失效场景
java·后端·spring
极客先躯10 小时前
高级java每日一道面试题-2026年03月30日-实战篇[Docker]-如何监控容器的网络流量?
java·运维·docker·容器·prometheus·高级面试
老迟到的茉莉10 小时前
Hermes 是谁?跟 Claude Code 差在哪
开发语言·python