享元模式详解
一、享元模式概述
享元模式(Flyweight Pattern)是一种结构型设计模式,它通过共享技术有效地支持大量细粒度的对象。享元模式通过分离对象的内在状态和外在状态,使得可以在多个对象之间共享内在状态,从而减少内存使用。
核心特点
- 共享对象:相同内在状态的对象被共享
- 状态分离:区分内在状态(共享)和外在状态(非共享)
- 减少内存:显著降低大量对象的内存占用
- 性能优化:适用于对象数量庞大的场景
二、享元模式的结构
主要角色
- Flyweight:抽象享元类,定义共享接口
- ConcreteFlyweight:具体享元类,实现共享部分
- UnsharedConcreteFlyweight:非共享享元类(可选)
- FlyweightFactory:享元工厂,创建和管理享元对象
- Client:客户端,维护外在状态
三、享元模式的实现
1. 基本实现
// 抽象享元
public interface Flyweight {
void operation(String extrinsicState);
}
// 具体享元
public class ConcreteFlyweight implements Flyweight {
private String intrinsicState;
public ConcreteFlyweight(String intrinsicState) {
this.intrinsicState = intrinsicState;
}
public void operation(String extrinsicState) {
System.out.println("内部状态: " + intrinsicState +
", 外部状态: " + extrinsicState);
}
}
// 享元工厂
public class FlyweightFactory {
private 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 int getFlyweightCount() {
return flyweights.size();
}
}
// 使用示例
FlyweightFactory factory = new FlyweightFactory();
Flyweight fw1 = factory.getFlyweight("A");
fw1.operation("First Call");
Flyweight fw2 = factory.getFlyweight("A");
fw2.operation("Second Call");
System.out.println("享元对象数量: " + factory.getFlyweightCount());
2. 更复杂的实现
// 字符享元
public class CharacterFlyweight {
private final char character;
private final String fontFamily;
private final int fontSize;
public CharacterFlyweight(char character, String fontFamily, int fontSize) {
this.character = character;
this.fontFamily = fontFamily;
this.fontSize = fontSize;
}
public void display(int x, int y, String color) {
System.out.printf("显示字符 '%c' (字体: %s %dpx) 在位置 (%d,%d) 颜色 %s\n",
character, fontFamily, fontSize, x, y, color);
}
}
// 享元工厂
public class CharacterFactory {
private Map<String, CharacterFlyweight> pool = new HashMap<>();
public CharacterFlyweight getCharacter(char c, String fontFamily, int fontSize) {
String key = c + fontFamily + fontSize;
if (!pool.containsKey(key)) {
pool.put(key, new CharacterFlyweight(c, fontFamily, fontSize));
}
return pool.get(key);
}
}
// 客户端使用
CharacterFactory factory = new CharacterFactory();
CharacterFlyweight char1 = factory.getCharacter('A', "Arial", 12);
char1.display(10, 20, "Red");
CharacterFlyweight char2 = factory.getCharacter('A', "Arial", 12);
char2.display(30, 40, "Blue");
四、享元模式的应用场景
1. 文本编辑器中的字符处理
public class TextEditor {
private CharacterFactory charFactory = new CharacterFactory();
private List<CharacterFlyweight> chars = new ArrayList<>();
private List<Integer> positionsX = new ArrayList<>();
private List<Integer> positionsY = new ArrayList<>();
private List<String> colors = new ArrayList<>();
public void addCharacter(char c, String font, int size, int x, int y, String color) {
CharacterFlyweight charFlyweight = charFactory.getCharacter(c, font, size);
chars.add(charFlyweight);
positionsX.add(x);
positionsY.add(y);
colors.add(color);
}
public void render() {
for (int i = 0; i < chars.size(); i++) {
chars.get(i).display(positionsX.get(i), positionsY.get(i), colors.get(i));
}
}
}
2. 游戏中的粒子系统
// 粒子享元
public class ParticleFlyweight {
private final String texture;
private final String particleShape;
public ParticleFlyweight(String texture, String shape) {
this.texture = texture;
this.particleShape = shape;
}
public void render(int x, int y, String color, int speed) {
System.out.printf("渲染 %s 粒子 (纹理: %s) 在 (%d,%d) 颜色 %s 速度 %d\n",
particleShape, texture, x, y, color, speed);
}
}
// 粒子工厂
public class ParticleFactory {
private Map<String, ParticleFlyweight> particles = new HashMap<>();
public ParticleFlyweight getParticle(String texture, String shape) {
String key = texture + "_" + shape;
if (!particles.containsKey(key)) {
particles.put(key, new ParticleFlyweight(texture, shape));
}
return particles.get(key);
}
}
3. 数据库连接池
public class ConnectionPool {
private static final int POOL_SIZE = 5;
private List<Connection> pool = new ArrayList<>();
public ConnectionPool(String url, String user, String password) {
for (int i = 0; i < POOL_SIZE; i++) {
pool.add(createConnection(url, user, password));
}
}
public Connection getConnection() {
for (Connection conn : pool) {
if (!conn.isInUse()) {
conn.setInUse(true);
return conn;
}
}
throw new RuntimeException("连接池已满");
}
public void releaseConnection(Connection conn) {
conn.setInUse(false);
}
private Connection createConnection(String url, String user, String password) {
// 创建实际连接
return new Connection(url, user, password);
}
}
五、享元模式的变体
1. 复合享元模式
public class CompositeFlyweight implements Flyweight {
private Map<String, Flyweight> flyweights = new HashMap<>();
public void add(String key, Flyweight flyweight) {
flyweights.put(key, flyweight);
}
public void operation(String extrinsicState) {
for (Flyweight flyweight : flyweights.values()) {
flyweight.operation(extrinsicState);
}
}
}
2. 带缓存清理的享元工厂
public class ManagedFlyweightFactory {
private Map<String, WeakReference<Flyweight>> cache = new HashMap<>();
public Flyweight getFlyweight(String key) {
synchronized (cache) {
WeakReference<Flyweight> ref = cache.get(key);
Flyweight flyweight = (ref != null) ? ref.get() : null;
if (flyweight == null) {
flyweight = new ConcreteFlyweight(key);
cache.put(key, new WeakReference<>(flyweight));
}
return flyweight;
}
}
}
六、享元模式的优缺点
优点
- 减少内存使用:共享相同内在状态的对象
- 提高性能:减少对象创建和垃圾回收开销
- 集中管理:享元工厂统一管理共享对象
- 扩展性好:可以轻松增加新的共享对象
缺点
- 增加复杂度:需要区分内在和外在状态
- 线程安全问题:共享对象需要考虑线程安全
- 不适用所有场景:仅当对象数量庞大且内在状态可共享时有效
七、最佳实践
- 合理划分状态:明确区分内在和外在状态
- 使用工厂管理:集中管理享元对象的创建和共享
- 考虑线程安全:多线程环境下确保共享对象安全
- 性能监控:监控享元池的大小和效果
- 避免过度使用:仅在确实需要优化内存时使用
八、总结
享元模式是优化大量细粒度对象内存使用的有效方案,特别适用于:
- 系统需要创建大量相似对象
- 对象的大部分状态可以外部化
- 内存占用是系统瓶颈
- 需要集中管理共享资源
在实际开发中,享元模式常见于:
- 文本/图形编辑器
- 游戏开发(粒子、角色等)
- 数据库连接池
- 浏览器DOM节点管理
正确使用享元模式可以显著降低内存消耗,但需要注意不要过早优化,应在性能分析确认内存是瓶颈后再考虑使用此模式。