享元模式
概述
享元模式(Flyweight Pattern)是一种结构型设计模式,它通过共享技术有效地支持大量细粒度对象的复用。享元模式通过共享已经存在的对象来大幅度减少需要创建的对象数量,避免大量相似对象的开销,从而提高系统资源的利用率。
定义
享元模式:运用共享技术有效地支持大量细粒度对象的复用。
核心思想
- 共享:将对象的公共部分提取出来,多个对象共享这些公共部分
- 细粒度:对象被设计为细粒度的,可以共享
- 不可变:享元对象的状态是不可变的,避免并发问题
结构
角色定义
- Flyweight(抽象享元类):定义对象的内部状态和外部状态的接口或实现
- ConcreteFlyweight(具体享元类):实现抽象享元类定义的接口
- UnsharedConcreteFlyweight(非共享具体享元类):不能被共享的子类
- FlyweightFactory(享元工厂类):创建并管理享元对象,确保合理地共享享元对象
- Client(客户端):维持一个对享元的引用,计算或存储享元的外部状态
类图
┌─────────────────┐ creates ┌─────────────────────┐
│ FlyweightFactory│ ────────────► │ ConcreteFlyweight │
└─────────────────┘ └─────────────────────┘
│ ▲
│ uses │
▼ │
┌─────────────────┐ ┌─────────────────────┐
│ Client │ ─────────────► │ AbstractFlyweight │
└─────────────────┘ └─────────────────────┘
实现示例
示例 1:文字编辑器中的字符对象
// 抽象享元类
public abstract class Character {
protected char symbol;
protected int size;
protected String font;
public abstract void display(int x, int y);
}
// 具体享元类
public class ConcreteCharacter extends Character {
public ConcreteCharacter(char symbol, int size, String font) {
this.symbol = symbol;
this.size = size;
this.font = font;
}
@Override
public void display(int x, int y) {
System.out.println("显示字符 '" + symbol + "' 在位置 (" + x + "," + y +
") 使用字体 " + font + " 大小 " + size);
}
}
// 享元工厂类
public class CharacterFactory {
private Map<String, Character> characterPool = new HashMap<>();
public Character getCharacter(char symbol, int size, String font) {
String key = symbol + "_" + size + "_" + font;
if (!characterPool.containsKey(key)) {
characterPool.put(key, new ConcreteCharacter(symbol, size, font));
System.out.println("创建新字符对象: " + key);
} else {
System.out.println("复用已存在的字符对象: " + key);
}
return characterPool.get(key);
}
public int getPoolSize() {
return characterPool.size();
}
}
// 客户端
public class TextEditor {
private CharacterFactory factory = new CharacterFactory();
private List<Character> characters = new ArrayList<>();
public void addCharacter(char symbol, int size, String font, int x, int y) {
Character character = factory.getCharacter(symbol, size, font);
characters.add(character);
character.display(x, y);
}
public void showPoolInfo() {
System.out.println("字符池大小: " + factory.getPoolSize());
}
}
示例 2:围棋棋子
// 抽象享元类
public abstract class ChessPiece {
protected String color;
public abstract void place(int x, int y);
}
// 具体享元类
public class BlackChessPiece extends ChessPiece {
public BlackChessPiece() {
this.color = "黑色";
}
@Override
public void place(int x, int y) {
System.out.println(color + "棋子放置在位置 (" + x + "," + y + ")");
}
}
public class WhiteChessPiece extends ChessPiece {
public WhiteChessPiece() {
this.color = "白色";
}
@Override
public void place(int x, int y) {
System.out.println(color + "棋子放置在位置 (" + x + "," + y + ")");
}
}
// 享元工厂类
public class ChessPieceFactory {
private Map<String, ChessPiece> piecePool = new HashMap<>();
public ChessPiece getChessPiece(String color) {
if (!piecePool.containsKey(color)) {
if ("黑色".equals(color)) {
piecePool.put(color, new BlackChessPiece());
} else if ("白色".equals(color)) {
piecePool.put(color, new WhiteChessPiece());
}
System.out.println("创建新的" + color + "棋子");
} else {
System.out.println("复用已存在的" + color + "棋子");
}
return piecePool.get(color);
}
}
// 客户端
public class GoGame {
private ChessPieceFactory factory = new ChessPieceFactory();
public void placePiece(String color, int x, int y) {
ChessPiece piece = factory.getChessPiece(color);
piece.place(x, y);
}
}
使用场景
- 大量相似对象:系统中存在大量相似对象,需要缓冲池的场景
- 内存敏感:需要节省内存,提高性能的场景
- 对象状态分离:对象的大部分状态可以外部化,且可以将这些外部状态传入对象中
- 缓存:需要缓存大量对象的场景
典型应用
- 文字编辑器中的字符对象
- 图形编辑器中的图形对象
- 游戏中的棋子、子弹等对象
- 数据库连接池
- 线程池
优缺点
优点
- 内存效率:大幅减少内存使用,特别是当有大量相似对象时
- 性能提升:减少对象创建和销毁的开销
- 资源共享:通过共享机制提高资源利用率
- 扩展性好:易于扩展新的享元类型
缺点
- 复杂性增加:增加了系统的复杂性
- 状态管理:需要仔细管理内部状态和外部状态
- 线程安全:享元对象通常是不可变的,需要考虑线程安全问题
- 调试困难:共享对象可能导致调试困难
注意事项
- 状态分离:正确区分内部状态和外部状态
- 不可变性:享元对象应该是不可变的
- 工厂管理:使用工厂类管理享元对象的创建和获取
- 内存监控:注意监控享元池的大小,避免内存泄漏
与其他模式的关系
- 单例模式:享元工厂通常使用单例模式实现
- 组合模式:享元模式可以与组合模式结合使用
- 策略模式:享元对象可以包含策略对象
总结
享元模式是一种重要的结构型设计模式,特别适用于需要处理大量相似对象的场景。通过共享机制,它可以显著提高系统的性能和内存效率。在实际应用中,需要根据具体场景合理使用,并注意状态管理和线程安全等问题。
