【设计模式】结构型-享元模式

文章目录

  • 前言
  • 一、概念
  • 二、核心结构
  • [三、Java 代码实现(游戏枪支示例)](#三、Java 代码实现(游戏枪支示例))
    • [1. 抽象享元](#1. 抽象享元)
    • [2. 具体享元(内部状态:枪类型)](#2. 具体享元(内部状态:枪类型))
    • [3. 享元工厂(核心:缓存复用)](#3. 享元工厂(核心:缓存复用))
    • [4. 客户端(外部传入坐标)](#4. 客户端(外部传入坐标))
  • [四、内部状态 vs 外部状态](#四、内部状态 vs 外部状态)
  • 五、优缺点
  • 六、应用场景
  • [七、享元模式 VS 单例模式](#七、享元模式 VS 单例模式)
  • 八、总结

前言

在系统中,如果频繁创建大量相似、重复、细粒度 的对象,会极大消耗内存,导致GC频繁、性能下降。比如:游戏里的子弹、树木;系统中的常量、配置、连接;字符串、线程池、缓存......这些场景都有一个共同点:很多对象是重复的,完全可以复用 。享元模式就是专门解决大量重复对象导致的内存浪费 问题,是性能优化最经典的设计模式之一。


一、概念

享元模式(Flyweight Pattern) 是一种结构型设计模式 ,核心思想:
运用共享技术,高效支持大量细粒度对象的复用,减少内存占用,提升系统性能。

简单理解:

  • 有重复的对象,不重复创建 ,而是缓存起来复用
  • 把对象信息分为两部分:
    1. 内部状态(Intrinsic):可共享、不变、存储在享元内部(如:枪的类型、颜色)
    2. 外部状态(Extrinsic):不可共享、每次不同、由外部传入(如:坐标、伤害)

一句话:能共享的都共享,不能共享的外部传。


二、核心结构

  1. Flyweight(抽象享元)
    定义享元的公共接口,接收并作用于外部状态。
  2. ConcreteFlyweight(具体享元)
    实现接口,包含内部状态,可被共享
  3. FlyweightFactory(享元工厂)
    负责创建、管理、缓存享元对象,确保对象复用。
  4. Client(客户端)
    维护外部状态,通过工厂获取享元并使用。

三、Java 代码实现(游戏枪支示例)

场景:游戏中生成大量相同类型的枪(AK、M4、Sniper),只区分坐标不同。

1. 抽象享元

java 复制代码
public interface Gun {
    // externalState:外部状态(坐标)
    void shoot(int x, int y);
}

2. 具体享元(内部状态:枪类型)

java 复制代码
public class ConcreteGun implements Gun {

    // 内部状态:枪类型(共享)
    private String type;

    public ConcreteGun(String type) {
        this.type = type;
    }

    @Override
    public void shoot(int x, int y) {
        System.out.println("【" + type + "】在坐标(" + x + "," + y + ")射击");
    }
}

3. 享元工厂(核心:缓存复用)

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

public class GunFactory {

    // 缓存池:key=类型,value=享元对象
    private static Map<String, Gun> pool = new HashMap<>();

    public static Gun getGun(String type) {
        // 存在则复用
        if (pool.containsKey(type)) {
            return pool.get(type);
        }
        // 不存在则创建,放入缓存
        Gun gun = new ConcreteGun(type);
        pool.put(type, gun);
        System.out.println("=== 创建新枪:" + type);
        return gun;
    }

    // 查看池中数量
    public static int getGunCount() {
        return pool.size();
    }
}

4. 客户端(外部传入坐标)

java 复制代码
public class Client {
    public static void main(String[] args) {

        Gun gun1 = GunFactory.getGun("AK");
        gun1.shoot(10, 20);

        Gun gun2 = GunFactory.getGun("AK");
        gun2.shoot(30, 40);

        Gun gun3 = GunFactory.getGun("M4");
        gun3.shoot(50, 60);

        Gun gun4 = GunFactory.getGun("M4");
        gun4.shoot(70, 80);

        System.out.println("========================");
        System.out.println("实际创建对象数量:" + GunFactory.getGunCount());
    }
}

输出:

复制代码
=== 创建新枪:AK
【AK】在坐标(10,20)射击
【AK】在坐标(30,40)射击
=== 创建新枪:M4
【M4】在坐标(50,60)射击
【M4】在坐标(70,80)射击
========================
实际创建对象数量:2

射击1000次AK,也只创建1个AK对象


四、内部状态 vs 外部状态

状态 特点 存储位置 示例
内部状态 不变、可共享、不随环境变 享元对象内部 枪类型、颜色、配置
外部状态 变化、不可共享、随环境变 客户端外部传入 坐标、时间、用户ID

享元模式的精髓:分离变与不变。


五、优缺点

优点

  1. 大幅减少对象数量,节约内存,尤其适合大量重复对象。
  2. 统一管理共享对象,便于维护、控制、监控。
  3. 区分内部/外部状态,结构清晰,扩展性强。

缺点

  1. 需要区分内外状态,设计复杂度提高
  2. 共享后对象是全局可见,要注意线程安全。
  3. 维护共享池需要额外开销。

六、应用场景

只要满足:大量重复细粒度对象 + 可区分内外状态,都能用。

  1. 游戏开发
    • 子弹、特效、树木、角色皮肤、地图瓦片
  2. 基础组件
    • 字符串常量池、Integer 缓存、线程池、连接池
  3. 系统优化
    • 配置对象、字典数据、权限项、枚举
  4. UI 组件
    • 字体、颜色、样式、图标复用
  5. 中间件/框架
    • MyBatis 的 SqlSource 缓存
    • Spring 的单例 Bean
    • Tomcat 线程池

最经典:JDK String 常量池 / IntegerCache 就是享元模式!


七、享元模式 VS 单例模式

很多人会混淆,这里一句话区分:

  • 单例:一个类只能有一个对象。
    目的:保证全局唯一,控制实例数。
  • 享元:一个类可以有多个对象,按类型/key共享。
    目的:复用重复对象,节省内存

单例是享元的一种极端情况(只有一个共享对象)。


八、总结

  1. 享元模式 = 对象共享池 + 分离内外状态
  2. 核心:能共享绝不新建,大幅降低内存。
  3. 三要素:抽象享元、具体享元、享元工厂
  4. 最适合:大量重复、细粒度、可分类的对象场景。
  5. 性能优化、内存优化必备设计模式。
相关推荐
电子科技圈2 小时前
SmartDV展示汽车IP解决方案以赋能智驾创芯并加速规模化普及
嵌入式硬件·设计模式·硬件架构·软件工程·软件构建·设计规范
砍光二叉树3 小时前
【设计模式】结构型-桥接模式
设计模式·桥接模式
姓蔡小朋友4 小时前
Agent Skill设计模式
开发语言·javascript·设计模式
砍光二叉树4 小时前
【设计模式】结构型-外观模式
设计模式·外观模式
zhaoshuzhaoshu4 小时前
设计模式6大原则详细对比(含场景举例)
设计模式·设计语言
砍光二叉树4 小时前
【设计模式】行为型-观察者模式
java·观察者模式·设计模式
泯仲18 小时前
Ragent项目7种设计模式深度解析:从源码看设计模式落地实践
java·算法·设计模式·agent
WarrenMondeville20 小时前
1.Unity面向对象-单一职责原则
unity·设计模式·c#
bmseven20 小时前
23种设计模式 - 适配器模式(Adapter)
设计模式·适配器模式