👋hi,我不是一名外包公司的员工,也不会偷吃茶水间的零食,我的梦想是能写高端CRUD
🔥 2025本人正在沉淀中... 博客更新速度++
👍 欢迎点赞、收藏、关注,跟上我的更新节奏
🎵 当你的天空突然下了大雨,那是我在为你炸乌云
文章目录
- 一、入门
- 二、享元模式在框架源码中的运用
- [Java 中的 Integer 缓存](#Java 中的 Integer 缓存)
- [Java 中的 ThreadPoolExecutor](#Java 中的 ThreadPoolExecutor)
- 三、总结
一、入门
什么是享元模式?
享元模式(Flyweight Pattern)是一种结构型设计模式,旨在通过共享对象来减少内存使用,特别适用于存在大量相似对象的情况。
它的核心思想是将对象的内在状态 (不变的部分)与外在状态(变化的部分)分离,从而减少对象的数量。
为什么要有享元模式?
假设我们正在开发一个在线文档编辑器(类似Google Docs),用户可以在文档中插入大量字符。每个字符可能包含以下属性:
- 内在状态(不变的部分):字体、字号、颜色。
- 外在状态(变化的部分):字符的位置(行号、列号)。
没有使用享元模式的实现:
java
class Character {
private char value;
private String font;
private int size;
private String color;
private int row;
private int col;
public Character(char value, String font, int size, String color, int row, int col) {
this.value = value;
this.font = font;
this.size = size;
this.color = color;
this.row = row;
this.col = col;
}
public void print() {
System.out.println("Character: " + value + ", Font: " + font + ", Size: " + size + ", Color: " + color + ", Position: (" + row + ", " + col + ")");
}
}
存在问题:
- 内存占用过高:大量相似对象重复存储相同的内在状态。
- 性能下降:频繁创建和销毁对象导致内存分配和垃圾回收开销。
- 资源浪费:相同的内在状态被重复存储,导致资源浪费。
怎么实现享元模式?
享元模式有哪些构成:
- Flyweight(享元接口):定义享元对象的接口,通常包含一个操作外在状态的方法。
- ConcreteFlyweight(具体享元):实现享元接口,存储内在状态。
- FlyweightFactory(享元工厂):负责创建和管理享元对象,确保共享。
- Client(客户端):维护外在状态,并在需要时调用享元对象。
【案例】在线文档编辑器 - 改
Flyweight(享元接口) :FontStyle
接口。
java
interface FontStyle {
void applyStyle(char value, int row, int col);
}
ConcreteFlyweight(具体享元):具体享元:存储内在状态。
java
class ConcreteFontStyle implements FontStyle {
private String font;
private int size;
private String color;
public ConcreteFontStyle(String font, int size, String color) {
this.font = font;
this.size = size;
this.color = color;
}
@Override
public void applyStyle(char value, int row, int col) {
System.out.println("Character: " + value + ", Font: " + font + ", Size: " + size + ", Color: " + color + ", Position: (" + row + ", " + col + ")");
}
}
FlyweightFactory(享元工厂) :FlyweightFactory
享元工厂,管理共享的享元对象。
java
class FontStyleFactory {
private static final Map<String, FontStyle> styles = new HashMap<>();
public static FontStyle getStyle(String font, int size, String color) {
String key = font + size + color;
FontStyle style = styles.get(key);
if (style == null) {
style = new ConcreteFontStyle(font, size, color);
styles.put(key, style);
}
return style;
}
}
Client(客户端): Character
类,使用享元对象。
java
class Character {
private char value;
private int row;
private int col;
private FontStyle style;
public Character(char value, int row, int col, FontStyle style) {
this.value = value;
this.row = row;
this.col = col;
this.style = style;
}
public void print() {
style.applyStyle(value, row, col);
}
}
测试类FlyweightPatternDemo
。
java
public class FlyweightPatternDemo {
public static void main(String[] args) {
FontStyle style1 = FontStyleFactory.getStyle("Arial", 12, "Black");
FontStyle style2 = FontStyleFactory.getStyle("Arial", 12, "Black");
FontStyle style3 = FontStyleFactory.getStyle("Times New Roman", 14, "Red");
Character char1 = new Character('A', 1, 1, style1);
Character char2 = new Character('B', 1, 2, style2);
Character char3 = new Character('C', 2, 1, style3);
char1.print();
char2.print();
char3.print();
System.out.println("Are style1 and style2 the same object? " + (style1 == style2));
}
}
输出结果
shell
Character: A, Font: Arial, Size: 12, Color: Black, Position: (1, 1)
Character: B, Font: Arial, Size: 12, Color: Black, Position: (1, 2)
Character: C, Font: Times New Roman, Size: 14, Color: Red, Position: (2, 1)
Are style1 and style2 the same object? true
二、享元模式在框架源码中的运用
Java 中的 Integer 缓存
Java 对 Integer
类型也使用了享元模式,缓存了常用的整数值(默认范围为 -128 到 127)。
实现原理:
- 当使用
Integer.valueOf(int)
方法创建Integer
对象时,JVM 会检查值是否在缓存范围内。 - 如果在范围内,则返回缓存中的对象;否则创建新的对象。
java
Integer i1 = Integer.valueOf(127);
Integer i2 = Integer.valueOf(127);
Integer i3 = Integer.valueOf(128);
System.out.println(i1 == i2); // true,i1 和 i2 指向缓存中的同一个对象
System.out.println(i1 == i3); // false,i3 是堆中新创建的对象
享元模式的体现:
- 内在状态:整数值(如 127)。
- 外在状态:无。
- 共享机制:通过缓存共享常用的
Integer
对象。
Java 中的 ThreadPoolExecutor
在 Java 的线程池实现中,享元模式的思想被用于复用线程对象。
实现原理:
- 线程池维护一组线程(享元对象),这些线程可以被多个任务复用。
- 任务(外在状态)被提交到线程池中,由线程池分配线程执行。
java
ExecutorService executor = Executors.newFixedThreadPool(5);
for (int i = 0; i < 10; i++) {
executor.submit(() -> {
System.out.println("Task executed by " + Thread.currentThread().getName());
});
}
executor.shutdown();
享元模式的体现:
- 内在状态:线程对象。
- 外在状态:任务(
Runnable
或Callable
)。 - 共享机制:线程池通过复用线程对象来减少线程创建和销毁的开销。
三、总结
享元模式的优点
- 减少内存占用 :
- 通过共享内在状态,减少系统中对象的数量,从而降低内存使用。
- 特别适用于存在大量相似对象的场景。
- 提高性能 :
- 减少对象的创建和销毁次数,降低内存分配和垃圾回收的开销。
- 在需要频繁创建和销毁对象的场景中,性能提升尤为明显。
- 优化资源管理 :
- 集中管理共享对象,避免资源浪费。
- 便于统一修改和维护共享对象。
- 分离关注点 :
- 将内在状态和外在状态分离,使系统设计更加清晰。
- 客户端只需关注外在状态,享元对象负责处理内在状态。
享元模式的缺点
- 增加系统复杂性 :
- 需要明确区分内在状态和外在状态,增加了设计的复杂性。
- 客户端需要维护外在状态,并在调用享元对象时传递这些状态。
- 线程安全问题 :
- 如果享元对象被多个线程共享,可能需要额外的同步机制来保证线程安全。
- 这会增加代码的复杂性和运行开销。
- 不适用于所有场景 :
- 如果对象的状态大部分都是变化的,或者对象之间几乎没有共享的状态,享元模式可能不适用。
- 在这种情况下,强行使用享元模式可能会导致设计过度复杂。
享元模式的适用场景
享元模式特别适用于以下场景:
- 大量相似对象
- 系统中存在大量相似对象,且这些对象的状态可以分离为内在状态和外在状态。
- 例子:
- 文本编辑器中的字符对象。
- 游戏中的树木、石头、敌人等对象。
- 内存敏感的应用
- 在内存受限的环境中(如嵌入式系统、移动设备),需要优化内存使用。
- 例子:
- 嵌入式系统中的图形界面库。
- 移动应用中的资源管理。
- 性能敏感的应用
- 在需要高性能的场景中(如游戏、图形渲染),减少对象的创建和销毁开销。
- 例子:
- 游戏引擎中的粒子系统。
- 图形渲染中的材质和纹理管理。
- 资源共享的场景
- 需要集中管理共享资源,避免资源浪费。
- 例子:
- 数据库连接池。
- 线程池。
- 不可变对象的场景
- 对象的内在状态是不可变的,适合被共享。
- 例子:
- Java 中的
String
常量池。 - Java 中的
Integer
缓存。
- Java 中的