Java 设计模式・享元模式篇:从思想到代码实现
一、结构型模式
在面向对象的世界里,如何优雅地组织类与对象、构建更大的结构,是每一位开发者都会反复思考的问题。直接堆砌类和继承固然简单,但当业务复杂度上升、类间关系变得盘根错节时,这种方式就会让代码变得臃肿、难以维护。
结构型设计模式正是为了解决这一痛点而诞生的一套思想体系。它们关注如何将类或对象按某种布局组合成更大的结构,通过组合、代理、适配和装饰等手段,让代码更具灵活性、可复用性和可维护性。
在 Java 开发中,结构型模式主要包含以下 7 种经典实现:
- 代理模式 (Proxy):为其他对象提供一种代理以控制对这个对象的访问,实现延迟加载、权限控制等。Java 设计模式・代理模式篇:从思想到代码实现-CSDN博客
- 适配器模式 (Adapter):将一个类的接口转换成客户期望的另一个接口,让原本不兼容的类可以协同工作。Java 设计模式・适配器模式篇:从思想到代码实现-CSDN博客
- 装饰器模式 (Decorator):动态地给一个对象添加额外的职责,比生成子类更灵活地扩展功能。Java 设计模式・装饰器模式篇:从思想到代码实现-CSDN博客
- 桥接模式 (Bridge):将抽象部分与它的实现部分分离,使它们都可以独立地变化,避免类爆炸。Java 设计模式・桥接模式篇:从思想到代码实现-CSDN博客
- 外观模式 (Facade):为子系统中的一组接口提供一个统一的高层接口,使子系统更容易使用。Java 设计模式・外观模式篇:从思想到代码实现-CSDN博客
- 组合模式 (Composite):将对象组合成树形结构以表示 “部分 - 整体” 的层次关系,使客户端对单个对象和组合对象的使用具有一致性。Java 设计模式・组合模式篇:从思想到代码实现-CSDN博客
- 享元模式 (Flyweight):运用共享技术有效地支持大量细粒度的对象,减少内存占用。
二、享元模式
2.1 介绍
享元模式是一种结构型设计模式,它的核心定义是:运用共享技术来有效地支持大量细粒度对象的复用。就是当系统中需要创建大量相似或相同的对象时,不必为每个对象都分配独立的内存空间,而是将这些对象中相同的部分(称为 “内部状态”)提取出来共享,只为那些不同的部分(称为 “外部状态”) 保留个性化的存储空间。这样可以极大地减少内存中对象的数量,节省系统资源。
- 内部状态(Intrinsic State)
- 定义:对象中不会随环境改变而改变的、可共享的部分。
- 例子:比如围棋中的棋子,“黑色”“白色” 就是内部状态(所有黑棋的颜色都是黑,所有白棋都是白,不会变)。
- 外部状态(Extrinsic State)
- 定义:对象中随环境改变而改变的、不可共享的部分,通常由外部代码传入。
- 例子:围棋棋子的 “位置(x,y 坐标)” 就是外部状态(每个棋子的位置都不同,且会随下棋过程变化)。
2.2 角色
-
抽象享元角色(Flyweight):通常是一个接口或抽象类,在抽象享元类中声明了具体享元类公共的方法,这些方法可以向外界提供享元对象的内部数据(内部状态),同时也可以通过这些方法来设置外部数据(外部状态)。
-
具体享元(Concrete Flyweight)角色 :它实现了抽象享元类,称为享元对象;在具体享元类中为内部状态提供了存储空间。通常我们可以结合单例模式来设计具体享元类,为每一个具体享元类提供唯一的享元对象。
-
非享元(Unsharable Flyweight)角色 :并不是所有的抽象享元类的子类都需要被共享,不能被共享的子类可设计为非共享具体享元类;当需要一个非共享具体享元类的对象时可以直接通过实例化创建。
-
享元工厂(Flyweight Factory)角色 :负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。
三、代码实现(不含外部状态)
为了方便理解,本文采用中文定义类名
3.1 抽象享元角色
public abstract class 棋子 {
public abstract String getColor();
public void display() {
System.out.println("棋子颜色:" + getColor() );
}
}
3.2 具体享元角色
public class 白棋 extends 棋子{
@Override
public String getColor() {
return "白色";
}
}
public class 黑棋 extends 棋子{
@Override
public String getColor() {
return "黑色";
}
}
3.3 享元工厂角色
public class 棋子工厂 {
private static 棋子工厂 instance = new 棋子工厂();
private static HashMap<String, 棋子> pool;
private 棋子工厂() {
pool = new HashMap<>();
pool.put("黑色", new 黑棋());
pool.put("白色", new 白棋());
}
public static 棋子工厂 getInstance() {
return instance;
}
public 棋子 getChess(String color) {
return pool.get(color);
}
}
3.4 客户端
public class 客户端 {
public static void main(String[] args) {
棋子工厂 factory = 棋子工厂.getInstance();
棋子 棋子1 = factory.getChess("黑色");
棋子 棋子2 = factory.getChess("黑色");
棋子 棋子3 = factory.getChess("白色");
棋子 棋子4 = factory.getChess("白色");
System.out.println("棋子1和棋子2是否相等:" + (棋子1 == 棋子2));
System.out.println("棋子3和棋子4是否相等:" + (棋子3 == 棋子4));
棋子1.display();
棋子2.display();
棋子3.display();
棋子4.display();
}
}
棋子1和棋子2是否相等:true
棋子3和棋子4是否相等:true
棋子颜色:黑色
棋子颜色:黑色
棋子颜色:白色
棋子颜色:白色
Process finished with exit code 0
四、代码实现(含外部状态)
| 状态类型 | 代码载体 | 核心特征 | 职责归属 |
|---|---|---|---|
| 内部状态 | getColor() 抽象方法 |
固定、可共享、子类实现 | 由具体享元角色(白棋/黑棋)承载和实现 |
| 外部状态 | 复合棋子 compositeChess 参数(含x/y) |
可变、不可共享、临时传入 | 由非享元角色(复合棋子)存储,客户端传入使用 |
4.1 抽象享元角色
public abstract class 棋子 {
public abstract String getColor();
public void display(复合棋子 compositeChess) {
System.out.println("棋子颜色:" + getColor() + ",位置:" +
compositeChess.getX() + "," + compositeChess.getY());
}
}
4.2 非享元角色
public class 复合棋子 {
private 棋子 棋子;
private int x;
private int y;
public 复合棋子(棋子 棋子, int x, int y) {
this.棋子 = 棋子;
this.x = x;
this.y = y;
}
public void display() {
this.棋子.display(this.x, this.y);
}
}
4.3 具体享元角色,享元工厂角色不变
4.4 客户端
public class 客户端 {
public static void main(String[] args) {
棋子工厂 factory = 棋子工厂.getInstance();
棋子 棋子1 = factory.getChess("黑色");
棋子 棋子2 = factory.getChess("黑色");
棋子 棋子3 = factory.getChess("白色");
棋子 棋子4 = factory.getChess("白色");
棋子1.display(new 复合棋子(1,1));
棋子2.display(new 复合棋子(2,2));
棋子3.display(new 复合棋子(3,3));
棋子4.display(new 复合棋子(4,4));
}
}
棋子颜色:黑色,位置:1,1
棋子颜色:黑色,位置:2,2
棋子颜色:白色,位置:3,3
棋子颜色:白色,位置:4,4
Process finished with exit code 0
五、优缺点
5.1 优点
-
极大节省内存空间(核心优势) 享元模式的核心价值就是复用大量相似 / 相同的细粒度对象,只存储一份 “内部状态”,避免重复创建对象占用内存
-
减少对象创建 / 销毁的性能开销 创建和销毁对象(尤其是大量细粒度对象)会消耗 CPU 资源,享元模式通过复用已有对象,避免了频繁的对象创建 / 销毁操作,提升系统性能。
-
提高系统的可维护性将 “内部状态”(固定、可共享)和 “外部状态”(可变、不可共享)分离,职责边界清晰:
- 内部状态的修改只需改共享对象,无需修改所有相关对象;
- 外部状态由客户端管理,灵活调整,不影响共享对象的核心逻辑。
5.2 缺点
-
增加系统复杂度 为了实现共享,需要额外引入 “享元工厂” 来管理对象池,还要区分 “内部状态” 和 “外部状态”,增加了类的数量和代码逻辑的复杂度,对新手不友好。
-
读取共享对象的开销(潜在问题)如果享元工厂的对象池过大,查找共享对象时(比如从 Map 中获取)会产生一定的查找开销,虽然通常可以忽略,但极端场景下(如百万级对象池)会影响性能。
-
外部状态需要由客户端传入或由非享元角色存储,若外部状态过多、过复杂:
- 会增加客户端的代码负担(需要管理大量外部状态);
- 若外部状态和内部状态耦合,可能导致逻辑混乱,甚至出现线程安全问题(比如多线程修改同一外部状态)。
六、使用场景
适用场景
系统中有大量相似 / 相同对象、对象的大部分状态可外部化、追求内存优化时(如池化场景、海量细粒度对象场景)。
不适用场景
对象数量少、内 / 外部状态难以分离、追求代码简洁性优先于内存优化时。
七、对比
7.1 与单例模式对比
Java 设计模式・单例模式篇:从思想到代码实现-CSDN博客
| 维度 | 享元模式 | 单例模式 |
|---|---|---|
| 核心目标 | 复用多个相似对象,减少对象数量 | 保证某个类只有一个实例,全局唯一 |
| 对象数量 | 可以有多个共享实例(如黑棋、白棋) | 只能有 1 个实例 |
| 共享范围 | 按 “内部状态” 分组共享(颜色相同的棋子共享) | 全系统共享同一个实例 |
| 典型例子 | 围棋的黑棋 / 白棋、字体对象 | 配置管理器、日志工厂 |
7.2 与原型模式对比
Java 设计模式・原型模式篇:从思想到代码实现-CSDN博客
| 维度 | 享元模式 | 原型模式 |
|---|---|---|
| 核心目标 | 复用已有对象,避免重复创建 | 快速创建新对象(克隆),减少创建开销 |
| 对象状态 | 区分内部 / 外部状态,内部状态共享 | 克隆出的对象是独立实例,状态可自定义 |
| 内存策略 | 减少对象数量,节省内存 | 不减少对象数量,只提升创建效率 |
7.3 与装饰者模式对比
Java 设计模式・装饰器模式篇:从思想到代码实现-CSDN博客
| 维度 | 享元模式 | 装饰器模式 |
|---|---|---|
| 核心目标 | 共享对象,节省内存 | 动态扩展对象功能,不改变核心结构 |
| 对象关系 | 多个场景复用同一个对象 | 装饰器包装原对象,形成 “对象链” |
| 状态处理 | 固化内部状态,外部状态动态传入 | 不改变原对象状态,只增加行为 |
八、源码举例 Integer
@jdk.internal.ValueBased
public final class Integer extends Number
implements Comparable<Integer>, Constable, ConstantDesc {
...
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer[] cache;
static Integer[] archivedCache;
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
h = Math.max(parseInt(integerCacheHighPropValue), 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(h, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
// Load IntegerCache.archivedCache from archive, if possible
CDS.initializeFromArchive(IntegerCache.class);
int size = (high - low) + 1;
// Use the archived cache if it exists and is large enough
if (archivedCache == null || size > archivedCache.length) {
Integer[] c = new Integer[size];
int j = low;
for(int i = 0; i < c.length; i++) {
c[i] = new Integer(j++);
}
archivedCache = c;
}
cache = archivedCache;
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
...
}
- nteger 类内置了
IntegerCache(享元工厂),缓存了-128 ~ 127范围内的 Integer 对象; - 当通过
Integer.valueOf(int)获取对象时,优先从缓存中取共享对象,超出范围才新建。
public class IntegerFlyweightDemo {
public static void main(String[] args) {
// 127在缓存范围内,复用同一个对象(享元)
Integer a = Integer.valueOf(127);
Integer b = Integer.valueOf(127);
System.out.println(a == b); // true(同一对象)
// 128超出缓存范围,创建新对象
Integer c = Integer.valueOf(128);
Integer d = Integer.valueOf(128);
System.out.println(c == d); // false(不同对象)
// 注意:new Integer()会强制新建,不走享元
Integer e = new Integer(127);
System.out.println(a == e); // false
}
}
九、其他相关设计模式
更多推荐




所有评论(0)