设计模式之享元模式 (Flyweight Pattern)

📋 Research Summary

享元模式是处理"海量细粒度对象"内存问题的经典方案。在文本编辑器(字符共享)、游戏引擎(粒子系统、树木渲染)中广泛应用。Java 的 String.intern()、Python 的小整数缓存都是其体现。


🌱 逻辑原点

如果游戏需要渲染 100 万棵树,每棵树都有位置、大小、纹理等属性,但树的种类只有 5 种,你真的需要创建 100 万个对象吗?

对象数量与内存容量的矛盾:实例数量巨大,但大部分状态是重复且不变的。

🧠 苏格拉底式对话

1️⃣ 现状:最原始的解法是什么?

为每个对象创建独立实例:

python 复制代码
class Tree:
    def __init__(self, x, y, name, color, texture):
        self.x = x          # 外部状态:每个树不同
        self.y = y          # 外部状态
        self.name = name    # 内部状态:大量重复
        self.color = color  # 内部状态
        self.texture = texture  # 内部状态(可能占用大量内存)

# 100 万棵树
forest = [Tree(x, y, "Oak", "Green", oak_texture) for x, y in positions]

优点 :简单直接,每个对象独立。
问题:内存占用巨大,大量重复数据。

2️⃣ 瓶颈:规模扩大 100 倍时会在哪里崩溃?

当需要渲染 1000 万棵树时:

  • 内存爆炸:每棵树的纹理数据 10MB,1000 万棵需要 100TB 内存!
  • GC 压力:大量对象导致垃圾回收频繁,程序卡顿
  • 缓存失效:CPU 缓存无法容纳这么多对象,频繁访问主存
  • 启动时间:加载所有纹理需要数小时

核心矛盾:我们需要 1000 万个"标识"(每棵树的位置不同),但只需要 5 个"类型"(树的种类)。

3️⃣ 突破:必须引入什么新维度?

分离内部状态与外部状态

不是"每棵树都是独立对象",而是"共享内部状态,传递外部状态"。内部状态(树的种类、纹理)只有 5 份,外部状态(位置)在使用时传入:

复制代码
享元工厂:管理 5 个 TreeType 对象(共享)
上下文:包含 x, y 和 TreeType 引用(轻量)
渲染时:TreeType.draw(x, y) -- 传入外部状态

这就是享元的本质:将对象状态分类,共享不变的部分,传递变化的部分

📊 视觉骨架

请求树 request tree
返回享元 return flyweight
缓存 cache
创建上下文 create context
引用 reference
包含 contain
客户端 Client
享元工厂 Flyweight Factory
树类型 TreeType
享元池 Pool
树上下文 Tree Context
外部状态 External State x,y

关键洞察:享元模式将对象分为"享元对象"(共享的内部状态)和"上下文"(独有的外部状态)。1000 万个上下文对象都很小(只包含坐标和引用),只有 5 个享元对象包含大纹理数据。

⚖️ 权衡模型

公式:

复制代码
Flyweight = 解决了海量对象的内存问题 + 牺牲了运行时性能 + 增加了状态管理复杂度

代价分析:

  • 解决: 大幅减少内存占用、减少对象创建开销、提高缓存命中率
  • 牺牲: 运行时性能(需要传入外部状态)、代码复杂度(需要区分内外状态)
  • ⚠️ 增加: 状态管理复杂度(外部状态需要由客户端维护或计算)、线程安全问题(共享对象的并发访问)

使用建议:当系统需要创建大量相似对象,且内存是瓶颈时使用。关键是识别出可以共享的内部状态。适合场景:字符渲染、游戏粒子、地图标注、连接池。

🔁 记忆锚点

python 复制代码
class TreeType:
    """
    享元对象:共享的内部状态
    """
    def __init__(self, name: str, texture: bytes):
        self.name = name
        self.texture = texture  # 大量数据,只存一份
    
    def draw(self, x: int, y: int) -> None:
        """外部状态由调用方传入"""
        render(self.texture, x, y)

class TreeFactory:
    """
    享元工厂:管理共享对象
    """
    _tree_types: Dict[str, TreeType] = {}
    
    @classmethod
    def get_tree_type(cls, name: str, texture: bytes) -> TreeType:
        """获取或创建享元对象"""
        if name not in cls._tree_types:
            cls._tree_types[name] = TreeType(name, texture)
        return cls._tree_types[name]

class Tree:
    """
    上下文:轻量级对象
    """
    def __init__(self, x: int, y: int, tree_type: TreeType):
        self.x = x  # 外部状态
        self.y = y  # 外部状态
        self._type = tree_type  # 引用享元对象
    
    def draw(self) -> None:
        """渲染时传入外部状态"""
        self._type.draw(self.x, self.y)

一句话本质: 享元模式 = 共享内部状态,传递外部状态,用计算换内存


相关推荐
sg_knight9 小时前
设计模式实战:模板方法模式(Template Method)
python·设计模式·模板方法模式
爱学习的程序媛10 小时前
【Web前端】JavaScript设计模式全解析
前端·javascript·设计模式·web
Yu_Lijing18 小时前
基于C++的《Head First设计模式》笔记——备忘录模式
c++·笔记·设计模式·备忘录模式
无籽西瓜a19 小时前
【西瓜带你学设计模式 | 第二期-观察者模式】观察者模式——推模型与拉模型实现、优缺点与适用场景
java·后端·观察者模式·设计模式
我真会写代码1 天前
Java程序员常用设计模式详解(实战版)
java·开发语言·设计模式
无籽西瓜a1 天前
【西瓜带你学设计模式 | 第一期-单例模式】单例模式——定义、实现方式、优缺点与适用场景以及注意事项
java·后端·单例模式·设计模式
cliffordl1 天前
设计模式(python)
python·设计模式
云道轩1 天前
告诉 Claude Code 在项目中遵循特定的编程模式/设计模式和技术栈约束
设计模式·ai·agent·claude code
花间相见2 天前
【Java基础面试题】—— 核心知识点面试题(含答案):语法+集合+JVM+设计模式+算法
java·jvm·设计模式
朱一头zcy2 天前
设计模式入门:最简单的模板方法模式
笔记·设计模式·模板方法模式