文章目录
- 0.个人感悟
- [1. 概念](#1. 概念)
- [2. 适配场景](#2. 适配场景)
-
- [2.1 适合的场景](#2.1 适合的场景)
- [2.2 常见场景举例](#2.2 常见场景举例)
- [3. 实现方法](#3. 实现方法)
-
- [3.1 实现思路](#3.1 实现思路)
- [3.2 UML类图](#3.2 UML类图)
- [3.3 代码示例](#3.3 代码示例)
- [4. 优缺点](#4. 优缺点)
-
- [4.1 优点](#4.1 优点)
- [4.2 缺点](#4.2 缺点)
- [5. 源码分析](#5. 源码分析)
0.个人感悟
- 说到享元,第一时间想到的是线程池、String常量池、包装类型缓存池等池化技术。这些都是运用共享技术来复用对象,减少对象开支
- 使用享元模式难点在于享元对象的确定和划分内部状态、外部状态,我的理解
- 享元对象: 享元享元,共享元数据,如果不同实例之间有差异,比如个别属性不同,但是有共性,那么可以将不同实例抽象成享元对象,而这些差异属性则划分为内部状态
- 内部状态: 享元对象自身的状态。可以区分享元对象实例。比如五子棋、围棋、跳棋,棋子进行享元,假如有7种颜色,那么对于棋子的实例而言,一种颜色是一个实例,第一个黑子和第二个黑子取的是同一个实例
- 外部状态: 享元对象依赖的对象,无法共享,不属于实例属性,比如棋子的位置信息
- 实际中,内部状态存储于享元对象内部,表现为享元对象属性,外部对象由客户端考虑和传入,通常是享元对象的方法参数
1. 概念
英文定义 (《设计模式:可复用面向对象软件的基础》)
Use sharing to support large numbers of fine-grained objects efficiently.
中文翻译
运用共享技术来有效地支持大量细粒度的对象。
理解
- 享元模式的核心是共享,通过共享来减少对象数量,节省内存空间
- 将对象的内部状态 (不变部分)和外部状态(可变部分)分离
- 内部状态:在多个对象间共享,存储在享元对象内部,不会随环境改变
- 外部状态:依赖于具体上下文,由客户端保存,在需要时传入享元对象
- 适用于系统中存在大量相似对象的情况,通过共享减少内存消耗
2. 适配场景
2.1 适合的场景
- 系统中有大量相似对象,导致内存开销过大
- 对象的大部分状态可以外部化,只有少量状态需要在对象内部维护
- 应用程序不依赖于对象标识(即不关心对象是否为同一个实例)
- 对象创建成本高,但使用频繁,希望通过共享减少创建开销
- 需要缓存某些计算结果或数据,避免重复计算
2.2 常见场景举例
- 文字编辑器:每个字符(字母、数字)的字体、颜色、大小等可以共享
- 图形系统:游戏中的粒子效果(火花、子弹),可以共享纹理、形状
- 数据库连接池:连接对象可以被多个客户端共享使用
- 网页浏览器:图片、CSS样式等资源可以缓存和共享
- 棋牌游戏:棋盘上的棋子对象可以共享形状、颜色等属性
3. 实现方法
3.1 实现思路
- 分离内部状态和外部状态:分析对象的哪些属性是不变的(内部状态),哪些是变化的(外部状态)
- 创建享元接口:定义可以接受外部状态并执行操作的方法
- 实现具体享元类:包含内部状态,并实现享元接口
- 创建享元工厂:管理享元对象池,确保对象被正确共享
- 客户端使用享元:客户端维护外部状态,需要时从工厂获取享元对象并传递外部状态
3.2 UML类图

角色说明:
- Flyweight(抽象享元):定义对象接口,可以接受外部状态
- ConcreteFlyweight(具体享元):实现抽象享元接口,存储内部状态
- UnsharedConcreteFlyweight(非共享享元):不需要共享的具体享元类
- FlyweightFactory(享元工厂):创建和管理享元对象,确保正确共享
- Client(客户端):维护外部状态,通过享元工厂获取享元对象
3.3 代码示例
背景:网站获取示例,支持新闻、博客形式,如果每次创建一个示例,那么会有很大开销,同时不易于代码复用,可以使用享元模式来解决
外部状态User
java
public class User {
private String name;
public User(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
抽象享元
java
public abstract class WebSite {
/**
* @param user 用户
* @description 浏览
* @author bigHao
* @date 2026/1/13
**/
public abstract void see(User user);
}
具体享元
java
public class ConcreteWebSite extends WebSite {
// 内部状态
private String type;
public ConcreteWebSite(String type) {
this.type = type;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
@Override
public void see(User user) {
System.out.println("ConcreteWebSite,webSite type:" + type + ", user:" + user.getName());
}
}
工厂
java
public class WebSiteFactory {
public static final String BLOG = "blog";
public static final String NEWS = "news";
// 池
private Map<String, WebSite> webSiteMap = new HashMap<String, WebSite>(2);
/**
* @param type 网站类型
* @return designpattern.flyweight.WebSite
* @description 获取网站实例
* @author bigHao
* @date 2026/1/13
**/
public WebSite getWebSite(String type) {
// 存在则直接取,不存在则放入,保持只有一个类型实例
webSiteMap.putIfAbsent(type, new ConcreteWebSite(type));
return webSiteMap.get(type);
}
/**
* @return int 数量
* @description 获取网站实例数量
* @author bigHao
* @date 2026/1/13
**/ public int getWebSiteCount() {
return webSiteMap.size();
}
}
客户端
java
public class Client {
static void main() {
// 工厂
WebSiteFactory webSiteFactory = new WebSiteFactory();
// 实例1
WebSite webSite1 = webSiteFactory.getWebSite(WebSiteFactory.BLOG);
webSite1.see(new User("user1"));
System.out.println("网站实例数量: " + webSiteFactory.getWebSiteCount());
// 实例2
WebSite webSite2 = webSiteFactory.getWebSite(WebSiteFactory.NEWS);
webSite1.see(new User("user2"));
System.out.println("网站实例数量: " + webSiteFactory.getWebSiteCount());
// 实例3
WebSite webSite3 = webSiteFactory.getWebSite(WebSiteFactory.BLOG);
webSite1.see(new User("user3"));
System.out.println("网站实例数量: " + webSiteFactory.getWebSiteCount());
// 实例4
WebSite webSite4 = webSiteFactory.getWebSite(WebSiteFactory.NEWS);
webSite1.see(new User("user4"));
System.out.println("网站实例数量: " + webSiteFactory.getWebSiteCount());
}
}
输出
ConcreteWebSite,webSite type:blog, user:user1
网站实例数量: 1
ConcreteWebSite,webSite type:news, user:user2
网站实例数量: 2
ConcreteWebSite,webSite type:blog, user:user3
网站实例数量: 2
ConcreteWebSite,webSite type:news, user:user4
网站实例数量: 2
4. 优缺点
4.1 优点
节约内存,提高性能:
- 大量减少相似对象的创建,显著降低内存占用
- 减少对象的创建和销毁开销,提高系统性能
提高复用性: - 享元对象可以被多个客户端共享使用
- 内部状态被复用,避免重复存储相同数据
增强扩展性: - 可以轻松添加新的享元类型
- 外部状态的改变不会影响享元对象本身
符合单一职责原则: - 将内部状态和外部状态分离,每个类职责更清晰
- 享元对象专注于内部状态,客户端专注于外部状态
提高可维护性: - 通过享元工厂集中管理对象创建,便于统一维护
- 享元池的状态可以监控和调整
4.2 缺点
增加系统复杂度:
- 需要分离内部状态和外部状态,设计更复杂
- 增加了享元工厂的管理逻辑
线程安全问题: - 共享的享元对象可能被多个线程同时访问
- 需要确保享元对象的线程安全性
外部状态管理复杂: - 客户端需要维护和管理外部状态
- 外部状态的传递和同步可能带来复杂性
可能违反封装原则: - 外部状态需要暴露给享元对象,可能破坏封装性
- 享元对象需要了解外部状态的接口
不适用于所有场景: - 如果对象的状态大部分都是可变的,享元模式的收益有限
- 如果对象间的差异很大,共享的意义不大
5. 源码分析
JDK中Integer类的缓存机制是享元模式的典型应用
跟踪代码可以看到其中使用了缓存数组
java
// Integer类源码分析
public final class Integer extends Number implements Comparable<Integer> {
// 内部类:整数缓存
private static class IntegerCache {
static final int low = -128; // 缓存下限
static final int high; // 缓存上限
static final Integer[] cache; // 缓存数组
static {
// 默认上限是127
int h = 127;
// 可以通过JVM参数调整上限
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// 最大数组大小限制
h = Math.min(i, Integer.MAX_VALUE - (-low) - 1);
} catch (NumberFormatException nfe) {
// 如果配置无效,忽略
}
}
high = h;
// 初始化缓存数组
cache = new Integer[(high - low) + 1];
int j = low;
for (int k = 0; k < cache.length; k++) {
cache[k] = new Integer(j++);
}
}
private IntegerCache() {} // 私有构造函数
}
// valueOf方法:使用缓存(享元工厂方法)
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high) {
// 如果在缓存范围内,返回缓存对象
return IntegerCache.cache[i + (-IntegerCache.low)];
}
// 否则创建新对象
return new Integer(i);
}
// 其他方法...
}
角色分析 :
内部状态和外部状态的分离:
- 内部状态:包装对象的值(如Integer的int值)是不可变的
- 外部状态 :无(因为这些对象不需要外部状态)
享元工厂: - 各个包装类的内部缓存类(IntegerCache、LongCache等)
- 通过静态初始化块创建缓存对象
客户端使用: - 通过
valueOf()方法获取享元对象 - 自动判断是否使用缓存对象
设计优势:
- 显著减少内存占用:大量使用小整数和字符时,避免重复创建对象
- 提高性能:对象创建和垃圾回收的开销大大减少
参考: