什么是享元模式
享元模式(Flyweight Pattern)是一种结构型设计模式,它通过共享技术有效地支持大量细粒度对象的复用。享元模式的核心思想是:当系统中存在大量相似对象时,将这些对象的内部状态共享,仅区分它们的外部状态,从而减少内存占用,提高系统性能。
享元这个术语源自拳击比赛中的"蝇量级"(最轻的级别),暗示这些共享对象非常轻量,以至于可以创建大量实例而不会导致性能问题。
享元模式解决的问题
享元模式主要解决以下问题:
- 内存使用效率问题:当需要创建大量相似对象时,传统方法会占用大量内存
- 对象创建开销问题:创建大量对象会带来显著的性能开销
- 状态管理复杂性:当对象数量庞大时,状态管理变得困难
享元模式的结构
享元模式包含以下几个核心角色:
- 享元接口(Flyweight):定义享元对象的接口,通过这个接口享元可以接受并作用于外部状态
- 具体享元(Concrete Flyweight):实现享元接口,存储内部状态,必须是可共享的
- 非共享具体享元(Unshared Concrete Flyweight):不需要共享的享元实现
- 享元工厂(Flyweight Factory):创建并管理享元对象,确保享元对象被正确地共享
- 客户端(Client):维护对享元的引用,计算或存储享元的外部状态
下面是享元模式的UML类图:
内部状态与外部状态
在享元模式中,对象的状态被分为两种:
-
内部状态(Intrinsic State):
- 存储在享元对象内部
- 不会随环境改变而改变
- 可以共享的部分
- 通常在享元对象创建时设置,之后不再修改
-
外部状态(Extrinsic State):
- 依赖于和随环境改变而改变的状态
- 不可以共享的部分
- 由客户端保存,在调用享元对象的方法时传递给享元
- 每次调用可能不同
区分内部状态和外部状态是享元模式的关键:内部状态使对象可共享,外部状态使共享对象在不同场景下可以有不同表现。
享元模式的基本实现
下面是享元模式的基本Java实现:
// 享元接口
interface Flyweight {
void operation(String extrinsicState);
}
// 具体享元类
class ConcreteFlyweight implements Flyweight {
private final String intrinsicState; // 内部状态
public ConcreteFlyweight(String intrinsicState) {
this.intrinsicState = intrinsicState;
System.out.println("创建具体享元对象: " + this.intrinsicState);
}
@Override
public void operation(String extrinsicState) {
System.out.println("具体享元" + intrinsicState + "执行操作,外部状态: " + extrinsicState);
}
}
// 非共享具体享元类
class UnsharedConcreteFlyweight implements Flyweight {
private final String allState; // 所有状态(没有区分内部和外部)
public UnsharedConcreteFlyweight(String allState) {
this.allState = allState;
System.out.println("创建非共享具体享元对象: " + this.allState);
}
@Override
public void operation(String extrinsicState) {
System.out.println("非共享具体享元" + allState + "执行操作,参数: " + extrinsicState);
}
}
// 享元工厂
class FlyweightFactory {
private final Map<String, Flyweight> flyweights = new HashMap<>();
// 获取享元对象
public Flyweight getFlyweight(String key) {
// 如果对象存在,则直接返回
if (flyweights.containsKey(key)) {
System.out.println("复用已有享元: " + key);
return flyweights.get(key);
}
// 如果对象不存在,则创建一个新的享元对象
Flyweight flyweight = new ConcreteFlyweight(key);
flyweights.put(key, flyweight);
return flyweight;
}
// 获取享元池中对象数量
public int getFlyweightCount() {
return flyweights.size();
}
}
// 客户端
public class FlyweightExample {
public static void main(String[] args) {
FlyweightFactory factory = new FlyweightFactory();
// 获取并使用共享享元对象
Flyweight flyweight1 = factory.getFlyweight("A");
flyweight1.operation("First Call");
Flyweight flyweight2 = factory.getFlyweight("B");
flyweight2.operation("Second Call");
Flyweight flyweight3 = factory.getFlyweight("A"); // 重复获取"A",将复用对象
flyweight3.operation("Third Call");
// 创建并使用非共享享元对象
Flyweight unsharedFlyweight = new UnsharedConcreteFlyweight("X");
unsharedFlyweight.operation("Unshared Call");
// 查看共享池中的对象数量
System.out.println("享元池中的对象数量: " + factory.getFlyweightCount());
// 验证对象共享
System.out.println("flyweight1 == flyweight3: " + (flyweight1 == flyweight3));
System.out.println("flyweight1 == flyweight2: " + (flyweight1 == flyweight2));
}
}
实际应用示例:字符渲染系统
下面通过一个字符渲染系统的例子来展示享元模式的实际应用:
import java.awt.*;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
// 字符享元接口
interface CharacterFlyweight {
void display(int fontSize, Point position, Color color);
}
// 具体字符享元类
class ConcreteCharacter implements CharacterFlyweight {
private final char symbol; // 内部状态 - 字符符号
private final Font baseFont; // 内部状态 - 基本字体
public ConcreteCharacter(char symbol, String fontFamily) {
this.symbol = symbol;
this.baseFont = new Font(fontFamily, Font.PLAIN, 10); // 基础大小为10
System.out.println("创建字符: '" + symbol + "' 使用字体: " + fontFamily);
}
@Override
public void display(int fontSize, Point position, Color color) {
// 外部状态:字体大小、位置和颜色
System.out.printf("显示字符: '%c' [字体: %s, 大小: %d, 位置: (%d,%d), 颜色: %s]%n",
symbol, baseFont.getFamily(), fontSize,
position.x, position.y, colorToHex(color));
}
private String colorToHex(Color color) {
return String.format("#%02x%02x%02x", color.getRed(), color.getGreen(), color.getBlue());
}
}
// 字符工厂 - 享元工厂
class CharacterFactory {
private final Map<Character, CharacterFlyweight> characters = new HashMap<>();
private final String[] fontFamilies = {"Arial", "Times New Roman", "Courier New"};
public CharacterFlyweight getCharacter(char symbol) {
// 检查该字符是否已存在
if (characters.containsKey(symbol)) {
return characters.get(symbol);
}
// 为不同的字符选择不同的字体(仅作演示)
String fontFamily = fontFamilies[Math.abs(symbol) % fontFamilies.length];
// 创建新的字符享元对象
CharacterFlyweight character = new ConcreteCharacter(symbol, fontFamily);
characters.put(symbol, character);
return character;
}
public int getCharacterCount() {
return characters.size();
}
}
// 文本区域 - 管理一组字符
class TextArea {
private final CharacterFactory factory;
private final Map<Point, CharData> characterData = new HashMap<>();
public TextArea(CharacterFactory factory) {
this.factory = factory;
}
// 添加一个字符到文本区域
public void addCharacterAt(char c, int fontSize, Point position, Color color) {
characterData.put(position, new CharData(c, fontSize, color));
}
// 渲染文本
public void render() {
System.out.println("\n===== 渲染文本区域 =====");
for (Map.Entry<Point, CharData> entry : characterData.entrySet()) {
Point position = entry.getKey();
CharData data = entry.getValue();
// 获取享元对象并调用其方法
CharacterFlyweight character = factory.getCharacter(data.symbol);
character.display(data.fontSize, position, data.color);
}
}
// 内部类,存储字符的外部状态
private static class CharData {
char symbol;
int fontSize;
Color color;
CharData(char symbol, int fontSize, Color color) {
this.symbol = symbol;
this.fontSize = fontSize;
this.color = color;
}
}
}
// 字符渲染系统示例
public class CharacterRenderingSystem {
public static void main(String[] args) {
CharacterFactory factory = new CharacterFactory();
TextArea textArea = new TextArea(factory);
Random random = new Random();
// 创建一些随机文本
String text = "Hello, Flyweight Pattern! This is a demonstration of memory efficiency.";
int x = 10, y = 10;
for (char c : text.toCharArray()) {
// 为每个字符设置不同的位置、大小和颜色(外部状态)
int fontSize = 12 + random.nextInt(10); // 12-21的字体大小
Color color = new Color(random.nextInt(256), random.nextInt(256), random.nextInt(256));
Point position = new Point(x, y);
textArea.addCharacterAt(c, fontSize, position, color);
x += fontSize / 2; // 水平位置根据字体大小调整
if (x > 500) { // 换行
x = 10;
y += 25;
}
}
// 渲染文本
textArea.render();
// 显示创建的字符数量
System.out.println("\n创建的唯一字符数量: " + factory.getCharacterCount());
System.out.println("文本总长度: " + text.length() + " 字符");
System.out.println("节省的对象数量: " + (text.length() - factory.getCharacterCount()));
}
}
实际应用示例:游戏中的粒子系统
下面通过一个游戏粒子系统的例子进一步说明享元模式的应用:
import java.awt.*;
import java.util.*;
import java.util.List;
// 粒子类型(享元接口)
interface ParticleType {
void render(Point position, Color color, float size, float rotation, float alpha);
String getTextureName();
}
// 具体粒子类型(具体享元)
class ConcreteParticleType implements ParticleType {
private final String textureName; // 内部状态 - 纹理名称
private final boolean isAdditive; // 内部状态 - 是否加法混合
private final int drawPriority; // 内部状态 - 绘制优先级
public ConcreteParticleType(String textureName, boolean isAdditive, int drawPriority) {
this.textureName = textureName;
this.isAdditive = isAdditive;
this.drawPriority = drawPriority;
System.out.println("创建粒子类型: " + textureName +
(isAdditive ? " (加法混合)" : " (普通混合)") +
" 优先级: " + drawPriority);
}
@Override
public void render(Point position, Color color, float size, float rotation, float alpha) {
// 外部状态:位置、颜色、大小、旋转和透明度
System.out.printf("渲染粒子 '%s' [位置: (%d,%d), 颜色: %s, 大小: %.1f, 旋转: %.1f°, 透明度: %.2f]%n",
textureName, position.x, position.y, colorToHex(color),
size, rotation, alpha);
}
@Override
public String getTextureName() {
return textureName;
}
private String colorToHex(Color color) {
return String.format("#%02x%02x%02x", color.getRed(), color.getGreen(), color.getBlue());
}
}
// 粒子类型工厂(享元工厂)
class ParticleTypeFactory {
private final Map<String, ParticleType> particleTypes = new HashMap<>();
public ParticleType getParticleType(String textureName, boolean isAdditive, int drawPriority) {
// 创建一个复合键
String key = textureName + "|" + isAdditive + "|" + drawPriority;
// 如果该粒子类型已存在,则直接返回
if (particleTypes.containsKey(key)) {
return particleTypes.get(key);
}
// 创建新的粒子类型
ParticleType type = new ConcreteParticleType(textureName, isAdditive, drawPriority);
particleTypes.put(key, type);
return type;
}
public int getTypeCount() {
return particleTypes.size();
}
}
// 粒子实例 - 包含外部状态
class Particle {
private ParticleType type; // 享元引用
private Point position; // 外部状态 - 位置
private Color color; // 外部状态 - 颜色
private float size; // 外部状态 - 大小
private float rotation; // 外部状态 - 旋转
private float alpha; // 外部状态 - 透明度
private float velocityX; // 外部状态 - X轴速度
private float velocityY; // 外部状态 - Y轴速度
private float lifespan; // 外部状态 - 生命周期
private float age; // 外部状态 - 当前年龄
public Particle(ParticleType type, Point position, Color color,
float size, float rotation, float alpha,
float velocityX, float velocityY, float lifespan) {
this.type = type;
this.position = position;
this.color = color;
this.size = size;
this.rotation = rotation;
this.alpha = alpha;
this.velocityX = velocityX;
this.velocityY = velocityY;
this.lifespan = lifespan;
this.age = 0;
}
// 更新粒子状态
public boolean update(float deltaTime) {
// 更新位置
position.x += velocityX * deltaTime;
position.y += velocityY * deltaTime;
// 更新年龄
age += deltaTime;
// 基于年龄更新透明度
alpha = 1.0f - (age / lifespan);
// 更新旋转
rotation += 45 * deltaTime; // 每秒旋转45度
// 检查粒子是否存活
return age < lifespan;
}
// 渲染粒子
public void render() {
type.render(position, color, size, rotation, alpha);
}
// 获取粒子类型
public ParticleType getType() {
return type;
}
}
// 粒子发射器 - 创建和管理粒子
class ParticleEmitter {
private final ParticleTypeFactory typeFactory;
private final List<Particle> activeParticles = new ArrayList<>();
private final Random random = new Random();
private final Point position;
private final String[] textureNames = {"smoke", "fire", "spark", "dust"};
public ParticleEmitter(ParticleTypeFactory typeFactory, Point position) {
this.typeFactory = typeFactory;
this.position = position;
}
// 发射新粒子
public void emit(int count) {
for (int i = 0; i < count; i++) {
// 随机选择粒子类型
String textureName = textureNames[random.nextInt(textureNames.length)];
boolean isAdditive = textureName.equals("fire") || textureName.equals("spark");
int drawPriority = isAdditive ? 2 : 1;
// 从工厂获取粒子类型
ParticleType type = typeFactory.getParticleType(textureName, isAdditive, drawPriority);
// 随机生成外部状态
Point particlePos = new Point(
position.x + random.nextInt(21) - 10,
position.y + random.nextInt(21) - 10
);
// 为不同类型的粒子设置不同的颜色
Color color;
switch (textureName) {
case "fire":
color = new Color(255, 100 + random.nextInt(156), 0);
break;
case "spark":
color = new Color(255, 200 + random.nextInt(56), 0);
break;
case "smoke":
int gray = 150 + random.nextInt(106);
color = new Color(gray, gray, gray);
break;
default:
color = new Color(
150 + random.nextInt(106),
150 + random.nextInt(106),
150 + random.nextInt(106)
);
}
float size = 5 + random.nextFloat() * 15;
float rotation = random.nextFloat() * 360;
float alpha = 0.7f + random.nextFloat() * 0.3f;
float velocityX = -5 + random.nextFloat() * 10;
float velocityY = -2 - random.nextFloat() * 3; // 向上移动
float lifespan = 1 + random.nextFloat() * 3; // 1-4秒生命周期
// 创建新粒子
Particle particle = new Particle(type, particlePos, color, size, rotation, alpha,
velocityX, velocityY, lifespan);
activeParticles.add(particle);
}
}
// 更新所有粒子
public void update(float deltaTime) {
Iterator<Particle> iterator = activeParticles.iterator();
while (iterator.hasNext()) {
Particle particle = iterator.next();
boolean isAlive = particle.update(deltaTime);
if (!isAlive) {
iterator.remove(); // 移除死亡的粒子
}
}
}
// 渲染所有粒子
public void render() {
System.out.println("\n===== 渲染粒子系统 (" + activeParticles.size() + " 个活跃粒子) =====");
for (Particle particle : activeParticles) {
particle.render();
}
}
// 获取活跃粒子数量
public int getActiveParticleCount() {
return activeParticles.size();
}
}
// 粒子系统示例
public class ParticleSystemExample {
public static void main(String[] args) {
ParticleTypeFactory typeFactory = new ParticleTypeFactory();
ParticleEmitter emitter = new ParticleEmitter(typeFactory, new Point(300, 300));
// 模拟游戏循环
float deltaTime = 0.1f; // 模拟每帧0.1秒
System.out.println("===== 开始粒子系统模拟 =====\n");
// 第1帧:发射一些粒子
System.out.println("帧 1: 初始发射粒子");
emitter.emit(20);
emitter.update(deltaTime);
emitter.render();
// 第5帧:发射更多粒子并更新现有粒子
System.out.println("\n帧 5: 发射更多粒子");
emitter.emit(15);
for (int i = 0; i < 4; i++) {
emitter.update(deltaTime);
}
emitter.render();
// 第10帧:最后一次发射并更新
System.out.println("\n帧 10: 最后一次发射");
emitter.emit(10);
for (int i = 0; i < 5; i++) {
emitter.update(deltaTime);
}
emitter.render();
// 第20帧:只更新现有粒子
System.out.println("\n帧 20: 更新现有粒子");
for (int i = 0; i < 10; i++) {
emitter.update(deltaTime);
}
emitter.render();
// 显示统计信息
System.out.println("\n===== 粒子系统统计 =====");
System.out.println("创建的粒子类型数量: " + typeFactory.getTypeCount());
System.out.println("当前活跃粒子数量: " + emitter.getActiveParticleCount());
System.out.println("节省的内存对象数量: " +
(emitter.getActiveParticleCount() - typeFactory.getTypeCount()));
}
}
享元模式在Java API中的应用
Java API中有多个享元模式的应用:
- Java基本数据类型的包装类:如
Integer、Boolean、Character等。例如,Integer.valueOf()方法会缓存-128到127之间的对象:
Integer a = Integer.valueOf(100);
Integer b = Integer.valueOf(100);
System.out.println(a == b); // 输出true,因为共享了同一个对象
Integer c = Integer.valueOf(1000);
Integer d = Integer.valueOf(1000);
System.out.println(c == d); // 输出false,超出了缓存范围
- 字符串常量池:Java的字符串字面量会被存储在字符串常量池中共享:
String s1 = "Hello";
String s2 = "Hello";
System.out.println(s1 == s2); // 输出true,s1和s2引用同一个字符串对象
- javax.swing中的边框和颜色对象:Swing中的
BorderFactory创建的边框对象和系统颜色对象都使用了享元模式。
享元模式的适用场景
享元模式在以下情况特别有用:
- 系统中存在大量相似对象:当系统中有大量相似对象,导致内存占用过高时
- 对象的状态可以分为内部状态和外部状态:部分状态可以共享,部分需要独立存储
- 创建对象的成本很高:对象的创建涉及复杂的计算或资源加载
- 程序需要缓冲池:需要维护一组可复用的对象
- 特定领域的应用:文本编辑器的字符渲染、游戏中的粒子系统、CAD系统中的图形元素等
享元模式与其他模式的比较
享元模式 vs 单例模式
- 享元模式:管理多个共享对象,每个对象有自己的内部状态
- 单例模式:确保一个类只有一个实例
享元模式 vs 对象池模式
- 享元模式:强调对象共享,对象在其整个生命周期内不会被销毁
- 对象池模式:强调对象复用,对象使用完成后放回池中供下次使用
享元模式 vs 缓存
- 享元模式:是一种设计模式,专注于减少对象数量和内存占用
- 缓存:是一种通用技术,关注数据访问性能,不一定涉及对象共享
享元模式 vs 组合模式
- 享元模式:关注对象共享和内存优化
- 组合模式:关注对象的树形结构组织
享元模式的优点
- 大幅减少内存使用:通过共享相同的对象状态,显著减少内存占用
- 提高性能:减少对象创建和垃圾回收的开销
- 集中状态管理:内部状态集中管理,易于维护
- 提高复杂系统的可行性:使得原本因为对象数量庞大而不可行的系统变得可行
享元模式的缺点
- 增加系统复杂性:区分内部状态和外部状态增加了设计复杂性
- 运行时间成本:将外部状态传递给享元对象可能增加运行时间开销
- 线程安全问题:对共享对象的并发访问需要考虑线程安全
- 调试困难:由于对象共享,调试可能更加困难
享元模式的最佳实践
- 正确区分内外部状态:确保内部状态可共享,外部状态由客户端管理
- 使用工厂管理享元对象:确保享元对象正确共享
- 考虑对象的不变性:享元对象最好是不可变的,确保线程安全
- 控制享元粒度:选择合适的粒度级别,不要过度细化导致管理复杂化
- 限制享元数量:考虑内存与性能的平衡,可能需要清理策略
- 考虑初始化成本:可以使用延迟加载或按需创建享元对象
- 注意享元的生命周期:享元通常和应用程序具有相同的生命周期
享元模式的性能考量
享元模式主要的性能考量包括:
- 内存vs时间权衡:内存节省是以时间成本为代价的,需要在两者之间找到平衡
- 对象创建成本:如果对象创建成本高,享元模式可以显著提高性能
- 外部状态传递开销:传递外部状态的开销可能影响性能
- 缓存局部性:频繁访问的对象应该保持良好的缓存局部性
- 同步开销:在多线程环境中,对享元工厂的同步可能带来额外开销
并发环境下的享元模式
在多线程环境中使用享元模式需要特别注意:
// 线程安全的享元工厂
class ConcurrentFlyweightFactory {
private final Map<String, Flyweight> flyweights = new ConcurrentHashMap<>();
public Flyweight getFlyweight(String key) {
// ConcurrentHashMap的操作是线程安全的
return flyweights.computeIfAbsent(key, k -> new ConcreteFlyweight(k));
}
}
或者使用双重检查锁定:
// 使用双重检查锁定的享元工厂
class ThreadSafeFlyweightFactory {
private final Map<String, Flyweight> flyweights = new HashMap<>();
public synchronized Flyweight getFlyweight(String key) {
if (!flyweights.containsKey(key)) {
flyweights.put(key, new ConcreteFlyweight(key));
}
return flyweights.get(key);
}
}
高级应用:多层次享元
在某些复杂系统中,可以实现多层次的享元模式:
// 多层次享元示例
// 基本形状享元
interface ShapeFlyweight {
void draw(Graphics g, Point position, Color color, float scale);
}
// 复合形状享元 - 由多个基本形状组成
class CompositeShape implements ShapeFlyweight {
private final List<ShapeComponent> components = new ArrayList<>();
// 添加基本形状
public void addComponent(ShapeFlyweight flyweight, Point relativePosition) {
components.add(new ShapeComponent(flyweight, relativePosition));
}
@Override
public void draw(Graphics g, Point position, Color color, float scale) {
for (ShapeComponent component : components) {
Point componentPos = new Point(
position.x + (int)(component.relativePosition.x * scale),
position.y + (int)(component.relativePosition.y * scale)
);
component.flyweight.draw(g, componentPos, color, scale);
}
}
// 内部类表示复合形状的组件
private static class ShapeComponent {
ShapeFlyweight flyweight;
Point relativePosition;
ShapeComponent(ShapeFlyweight flyweight, Point relativePosition) {
this.flyweight = flyweight;
this.relativePosition = relativePosition;
}
}
}
// 多层次享元工厂
class ShapeFactory {
private final Map<String, ShapeFlyweight> basicShapes = new HashMap<>();
private final Map<String, CompositeShape> compositeShapes = new HashMap<>();
// 获取基本形状
public ShapeFlyweight getBasicShape(String type) {
return basicShapes.computeIfAbsent(type, this::createBasicShape);
}
// 获取复合形状
public ShapeFlyweight getCompositeShape(String type) {
return compositeShapes.computeIfAbsent(type, this::createCompositeShape);
}
// 创建基本形状
private ShapeFlyweight createBasicShape(String type) {
// 实现创建基本形状的逻辑
// ...
}
// 创建复合形状
private CompositeShape createCompositeShape(String type) {
CompositeShape shape = new CompositeShape();
// 根据类型创建不同的复合形状
if ("tree".equals(type)) {
// 添加树干
shape.addComponent(getBasicShape("rectangle"), new Point(0, 20));
// 添加树冠
shape.addComponent(getBasicShape("circle"), new Point(0, 0));
shape.addComponent(getBasicShape("circle"), new Point(-10, 10));
shape.addComponent(getBasicShape("circle"), new Point(10, 10));
} else if ("house".equals(type)) {
// 添加房子的墙
shape.addComponent(getBasicShape("square"), new Point(0, 10));
// 添加房子的屋顶
shape.addComponent(getBasicShape("triangle"), new Point(0, -10));
}
return shape;
}
}
享元模式在现代编程中的应用
前端开发中的应用
在现代Web开发中,享元模式也有广泛应用:
// JavaScript中的享元模式示例
class Particle {
constructor(texture) {
this.texture = texture; // 内部状态
}
display(x, y, scale, rotation) {
console.log(`显示纹理 ${this.texture} 在位置(${x},${y}),缩放: ${scale},旋转: ${rotation}`);
}
}
class ParticleFactory {
constructor() {
this.particles = {};
}
getParticle(texture) {
if (!this.particles[texture]) {
this.particles[texture] = new Particle(texture);
console.log(`创建新的粒子纹理: ${texture}`);
}
return this.particles[texture];
}
}
// 使用享元模式
const factory = new ParticleFactory();
const particles = [];
// 创建1000个粒子,但只有5种不同的纹理
for (let i = 0; i < 1000; i++) {
const textureIndex = i % 5;
const texture = `texture${textureIndex}`;
// 获取享元对象
const particle = factory.getParticle(texture);
// 存储外部状态
particles.push({
flyweight: particle,
x: Math.random() * 500,
y: Math.random() * 500,
scale: 0.5 + Math.random(),
rotation: Math.random() * 360
});
}
// 渲染所有粒子
console.log("渲染1000个粒子:");
for (let i = 0; i < 10; i++) { // 为了简洁,只打印前10个
const p = particles[i];
p.flyweight.display(p.x, p.y, p.scale, p.rotation);
}
console.log(`创建的实际纹理对象数量: ${Object.keys(factory.particles).length}`);
console.log(`节省的对象数量: ${particles.length - Object.keys(factory.particles).length}`);
React中的享元模式
在React等现代前端框架中,享元模式的思想也有所体现:
// React中的享元理念
function ItemRenderer({ type, data }) {
// 根据类型选择不同的渲染器
const renderItem = (type) => {
switch(type) {
case 'text':
return <TextItem />;
case 'image':
return <ImageItem />;
case 'video':
return <VideoItem />;
default:
return <DefaultItem />;
}
};
const ItemComponent = renderItem(type);
// 渲染组件并传入外部状态(data)
return <ItemComponent data={data} />;
}
// 用法
function ItemList({ items }) {
return (
<div>
{items.map(item => (
<ItemRenderer
key={item.id}
type={item.type}
data={item}
/>
))}
</div>
);
}
享元模式在微服务架构中的应用
在微服务架构中,享元模式的思想也可以应用于连接池和服务实例管理:
// 微服务中的连接享元
class ServiceConnectionPool {
private final Map<String, ServiceConnection> connections = new ConcurrentHashMap<>();
private final ServiceConnectionFactory factory;
public ServiceConnectionPool(ServiceConnectionFactory factory) {
this.factory = factory;
}
public ServiceConnection getConnection(String serviceId) {
return connections.computeIfAbsent(serviceId, id -> {
System.out.println("创建到服务 " + id + " 的新连接");
return factory.createConnection(id);
});
}
public void closeConnection(String serviceId) {
ServiceConnection connection = connections.remove(serviceId);
if (connection != null) {
connection.close();
}
}
public int getActiveConnectionCount() {
return connections.size();
}
}
享元模式的变体
复合享元
复合享元模式允许将多个享元对象组合成一个更大的享元对象:
// 复合享元接口
interface CompositeFlyweight extends Flyweight {
void add(String key, Flyweight flyweight);
void remove(String key);
}
// 复合享元实现
class ConcreteCompositeFlyweight implements CompositeFlyweight {
private final Map<String, Flyweight> children = new HashMap<>();
@Override
public void operation(String extrinsicState) {
for (Flyweight flyweight : children.values()) {
flyweight.operation(extrinsicState);
}
}
@Override
public void add(String key, Flyweight flyweight) {
children.put(key, flyweight);
}
@Override
public void remove(String key) {
children.remove(key);
}
}
// 支持复合享元的工厂
class ExtendedFlyweightFactory {
private final Map<String, Flyweight> flyweights = new HashMap<>();
// 获取单个享元
public Flyweight getFlyweight(String key) {
if (!flyweights.containsKey(key)) {
flyweights.put(key, new ConcreteFlyweight(key));
}
return flyweights.get(key);
}
// 获取复合享元
public Flyweight getCompositeFlyweight(String... keys) {
CompositeFlyweight compositeFlyweight = new ConcreteCompositeFlyweight();
for (String key : keys) {
compositeFlyweight.add(key, getFlyweight(key));
}
return compositeFlyweight;
}
}
参数化享元
参数化享元通过函数参数传递内部状态,而不是在享元对象中存储:
// 参数化享元接口
interface ParameterizedFlyweight {
void operation(String intrinsicState, String extrinsicState);
}
// 参数化享元实现
class ConcreteParameterizedFlyweight implements ParameterizedFlyweight {
@Override
public void operation(String intrinsicState, String extrinsicState) {
System.out.println("参数化享元操作 - 内部状态: " + intrinsicState +
", 外部状态: " + extrinsicState);
}
}
// 参数化享元工厂
class ParameterizedFlyweightFactory {
private final ParameterizedFlyweight flyweight = new ConcreteParameterizedFlyweight();
public ParameterizedFlyweight getFlyweight() {
return flyweight;
}
}
享元模式与缓存策略
在实际应用中,享元池可能需要缓存管理策略,如LRU(最近最少使用)或LFU(最少使用频率):
// 带LRU缓存的享元工厂
class LRUFlyweightFactory<K, V> {
private final int capacity;
private final Map<K, V> cache = new LinkedHashMap<>();
private final Function<K, V> creator;
public LRUFlyweightFactory(int capacity, Function<K, V> creator) {
this.capacity = capacity;
this.creator = creator;
}
public synchronized V getFlyweight(K key) {
if (cache.containsKey(key)) {
// 移动到链表末尾(表示最近使用)
V value = cache.remove(key);
cache.put(key, value);
return value;
}
// 创建新的享元
V newValue = creator.apply(key);
// 如果缓存已满,移除最久未使用的项
if (cache.size() >= capacity) {
K oldestKey = cache.keySet().iterator().next();
cache.remove(oldestKey);
}
// 添加新创建的享元
cache.put(key, newValue);
return newValue;
}
public int getCacheSize() {
return cache.size();
}
}
享元模式与内存管理
享元模式对内存管理有重要影响,尤其是在处理大量对象的系统中:
// 内存敏感型享元工厂
class MemorySensitiveFlyweightFactory {
private final Map<String, WeakReference<Flyweight>> flyweights = new HashMap<>();
public Flyweight getFlyweight(String key) {
WeakReference<Flyweight> ref = flyweights.get(key);
Flyweight flyweight = (ref != null) ? ref.get() : null;
if (flyweight == null) {
flyweight = new ConcreteFlyweight(key);
flyweights.put(key, new WeakReference<>(flyweight));
}
return flyweight;
}
// 清理失效的引用
public void cleanUp() {
flyweights.entrySet().removeIf(entry -> entry.getValue().get() == null);
}
}
享元模式在数据库连接池中的应用
数据库连接池是享元模式的一个典型应用:
// 数据库连接享元
class DatabaseConnection {
private final String url;
private final String username;
private final String password;
private Connection connection;
private boolean inUse;
public DatabaseConnection(String url, String username, String password) {
this.url = url;
this.username = username;
this.password = password;
this.inUse = false;
}
public synchronized void open() throws SQLException {
if (connection == null || connection.isClosed()) {
connection = DriverManager.getConnection(url, username, password);
}
}
public synchronized void close() {
inUse = false;
}
public synchronized boolean isInUse() {
return inUse;
}
public synchronized void setInUse(boolean inUse) {
this.inUse = inUse;
}
public Connection getConnection() {
return connection;
}
}
// 数据库连接池
class DatabaseConnectionPool {
private final List<DatabaseConnection> connections = new ArrayList<>();
private final String url;
private final String username;
private final String password;
private final int maxConnections;
public DatabaseConnectionPool(String url, String username, String password, int maxConnections) {
this.url = url;
this.username = username;
this.password = password;
this.maxConnections = maxConnections;
}
public synchronized DatabaseConnection getConnection() throws SQLException {
// 查找空闲连接
for (DatabaseConnection conn : connections) {
if (!conn.isInUse()) {
conn.setInUse(true);
return conn;
}
}
// 如果没有空闲连接但未达到最大连接数,创建新连接
if (connections.size() < maxConnections) {
DatabaseConnection conn = new DatabaseConnection(url, username, password);
conn.open();
conn.setInUse(true);
connections.add(conn);
return conn;
}
// 无法获取连接
throw new SQLException("无法获取数据库连接:已达到最大连接数");
}
public synchronized void releaseConnection(DatabaseConnection conn) {
conn.setInUse(false);
}
public synchronized int getActiveConnectionCount() {
int count = 0;
for (DatabaseConnection conn : connections) {
if (conn.isInUse()) {
count++;
}
}
return count;
}
public synchronized int getTotalConnectionCount() {
return connections.size();
}
}
享元模式与对象池的关系
享元模式和对象池虽然相似,但有重要区别:
// 对象池示例
class ObjectPool<T> {
private final List<T> available = new ArrayList<>();
private final List<T> inUse = new ArrayList<>();
private final Supplier<T> objectFactory;
public ObjectPool(Supplier<T> objectFactory, int initialSize) {
this.objectFactory = objectFactory;
// 预创建对象
for (int i = 0; i < initialSize; i++) {
available.add(objectFactory.get());
}
}
public synchronized T borrow() {
if (available.isEmpty()) {
// 没有可用对象,创建一个新的
T newObject = objectFactory.get();
inUse.add(newObject);
return newObject;
} else {
// 使用现有对象
T object = available.remove(available.size() - 1);
inUse.add(object);
return object;
}
}
public synchronized void release(T object) {
if (inUse.contains(object)) {
inUse.remove(object);
available.add(object);
}
}
public synchronized int getAvailableCount() {
return available.size();
}
public synchronized int getInUseCount() {
return inUse.size();
}
}
性能测试:享元模式的效果
下面是一个简单的性能测试,对比使用享元模式和不使用享元模式的内存和性能差异:
// 性能测试类
public class FlyweightPerformanceTest {
// 不使用享元的类
static class StandardObject {
private String state;
public StandardObject(String state) {
this.state = state;
}
public void operation() {
// 模拟操作
}
}
// 使用享元的类
static class FlyweightObject {
private String intrinsicState;
public FlyweightObject(String intrinsicState) {
this.intrinsicState = intrinsicState;
}
public void operation(String extrinsicState) {
// 模拟操作
}
}
// 享元工厂
static class FlyweightFactory {
private Map<String, FlyweightObject> flyweights = new HashMap<>();
public FlyweightObject getFlyweight(String key) {
if (!flyweights.containsKey(key)) {
flyweights.put(key, new FlyweightObject(key));
}
return flyweights.get(key);
}
public int getCount() {
return flyweights.size();
}
}
public static void main(String[] args) {
final int OBJECT_COUNT = 1_000_000;
final int DISTINCT_STATES = 100;
System.out.println("开始性能测试 - 创建 " + OBJECT_COUNT + " 个对象...");
// 测试不使用享元
long startTimeStandard = System.currentTimeMillis();
List<StandardObject> standardObjects = new ArrayList<>(OBJECT_COUNT);
for (int i = 0; i < OBJECT_COUNT; i++) {
String state = "State" + (i % DISTINCT_STATES);
standardObjects.add(new StandardObject(state));
}
for (StandardObject obj : standardObjects) {
obj.operation();
}
long endTimeStandard = System.currentTimeMillis();
// 测试使用享元
long startTimeFlyweight = System.currentTimeMillis();
FlyweightFactory factory = new FlyweightFactory();
List<String> extrinsicStates = new ArrayList<>(OBJECT_COUNT);
for (int i = 0; i < OBJECT_COUNT; i++) {
String intrinsicState = "State" + (i % DISTINCT_STATES);
factory.getFlyweight(intrinsicState);
extrinsicStates.add("ExtrinsicState" + i);
}
int index = 0;
for (String state : extrinsicStates) {
String intrinsicState = "State" + (index % DISTINCT_STATES);
FlyweightObject flyweight = factory.getFlyweight(intrinsicState);
flyweight.operation(state);
index++;
}
long endTimeFlyweight = System.currentTimeMillis();
// 输出结果
System.out.println("\n===== 性能测试结果 =====");
System.out.println("标准对象:");
System.out.println(" - 创建对象数量: " + standardObjects.size());
System.out.println(" - 执行时间: " + (endTimeStandard - startTimeStandard) + "ms");
System.out.println("\n享元对象:");
System.out.println(" - 创建享元对象数量: " + factory.getCount());
System.out.println(" - 处理对象数量: " + extrinsicStates.size());
System.out.println(" - 执行时间: " + (endTimeFlyweight - startTimeFlyweight) + "ms");
System.out.println("\n内存节省: " + (OBJECT_COUNT - factory.getCount()) +
" 个对象 (" + (100.0 * (OBJECT_COUNT - factory.getCount()) / OBJECT_COUNT) + "%)");
}
}
总结
享元模式是一种优化内存使用的结构型设计模式,它通过共享相似对象的内部状态来减少内存占用,提高系统性能。当系统中存在大量相似对象,这些对象的状态可以分为内部状态和外部状态时,享元模式特别有用。
享元模式的核心概念包括:
- 区分内部状态和外部状态
- 通过享元工厂管理共享对象
- 客户端负责管理外部状态
在实际应用中,享元模式广泛应用于文本编辑器、图形系统、游戏开发、连接池管理等场景。Java API中的包装类、字符串常量池等也使用了享元模式的思想。
虽然享元模式可以显著减少内存使用并提高性能,但它也增加了系统的复杂性,并可能引入并发问题。因此,在使用享元模式时,需要权衡内存节省与系统复杂性之间的关系,确保正确区分内部状态和外部状态,并妥善处理享元对象的生命周期。
1万+

被折叠的 条评论
为什么被折叠?



