文章目录
- 前言
- 一、概念
- 二、核心结构
- [三、Java 代码实现(游戏枪支示例)](#三、Java 代码实现(游戏枪支示例))
-
- [1. 抽象享元](#1. 抽象享元)
- [2. 具体享元(内部状态:枪类型)](#2. 具体享元(内部状态:枪类型))
- [3. 享元工厂(核心:缓存复用)](#3. 享元工厂(核心:缓存复用))
- [4. 客户端(外部传入坐标)](#4. 客户端(外部传入坐标))
- [四、内部状态 vs 外部状态](#四、内部状态 vs 外部状态)
- 五、优缺点
- 六、应用场景
- [七、享元模式 VS 单例模式](#七、享元模式 VS 单例模式)
- 八、总结
前言
在系统中,如果频繁创建大量相似、重复、细粒度 的对象,会极大消耗内存,导致GC频繁、性能下降。比如:游戏里的子弹、树木;系统中的常量、配置、连接;字符串、线程池、缓存......这些场景都有一个共同点:很多对象是重复的,完全可以复用 。享元模式就是专门解决大量重复对象导致的内存浪费 问题,是性能优化最经典的设计模式之一。
一、概念
享元模式(Flyweight Pattern) 是一种结构型设计模式 ,核心思想:
运用共享技术,高效支持大量细粒度对象的复用,减少内存占用,提升系统性能。
简单理解:
- 有重复的对象,不重复创建 ,而是缓存起来复用;
- 把对象信息分为两部分:
- 内部状态(Intrinsic):可共享、不变、存储在享元内部(如:枪的类型、颜色)
- 外部状态(Extrinsic):不可共享、每次不同、由外部传入(如:坐标、伤害)
一句话:能共享的都共享,不能共享的外部传。
二、核心结构
- Flyweight(抽象享元)
定义享元的公共接口,接收并作用于外部状态。 - ConcreteFlyweight(具体享元)
实现接口,包含内部状态,可被共享。 - FlyweightFactory(享元工厂)
负责创建、管理、缓存享元对象,确保对象复用。 - 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 |
享元模式的精髓:分离变与不变。
五、优缺点
优点
- 大幅减少对象数量,节约内存,尤其适合大量重复对象。
- 统一管理共享对象,便于维护、控制、监控。
- 区分内部/外部状态,结构清晰,扩展性强。
缺点
- 需要区分内外状态,设计复杂度提高。
- 共享后对象是全局可见,要注意线程安全。
- 维护共享池需要额外开销。
六、应用场景
只要满足:大量重复细粒度对象 + 可区分内外状态,都能用。
- 游戏开发
- 子弹、特效、树木、角色皮肤、地图瓦片
- 基础组件
- 字符串常量池、Integer 缓存、线程池、连接池
- 系统优化
- 配置对象、字典数据、权限项、枚举
- UI 组件
- 字体、颜色、样式、图标复用
- 中间件/框架
- MyBatis 的
SqlSource缓存 - Spring 的单例 Bean
- Tomcat 线程池
- MyBatis 的
最经典:JDK String 常量池 / IntegerCache 就是享元模式!
七、享元模式 VS 单例模式
很多人会混淆,这里一句话区分:
- 单例:一个类只能有一个对象。
目的:保证全局唯一,控制实例数。 - 享元:一个类可以有多个对象,按类型/key共享。
目的:复用重复对象,节省内存。
单例是享元的一种极端情况(只有一个共享对象)。
八、总结
- 享元模式 = 对象共享池 + 分离内外状态
- 核心:能共享绝不新建,大幅降低内存。
- 三要素:抽象享元、具体享元、享元工厂。
- 最适合:大量重复、细粒度、可分类的对象场景。
- 是性能优化、内存优化必备设计模式。