享元模式(Flyweight Pattern)学习笔记
1. 模式定义
结构型设计模式,通过共享技术实现大量细粒度对象的复用,有效减少内存占用并提高性能。核心思想:分离内部状态(可共享)与外部状态(不可共享)
2. 适用场景
✅ 系统中存在大量相似对象
✅ 对象的大部分状态可以外部化
✅ 需要缓存池或对象池的应用
✅ 需要减少内存占用和提高性能的场景
3. 模式结构
4. 核心角色
角色 | 说明 |
---|---|
Flyweight | 抽象享元接口,定义操作接口 |
ConcreteFlyweight | 具体享元类,包含内部状态(可共享) |
UnsharedConcreteFlyweight | 非共享具体享元类(可选) |
FlyweightFactory | 享元工厂类,管理享元对象池 |
Client | 客户端,维护外部状态并调用享元对象 |
5. 状态说明
状态类型 | 说明 |
---|---|
内部状态 | 存储在享元对象内部,可以被多个上下文共享的信息 |
外部状态 | 随上下文变化的信息,由客户端保存并在调用时传入 |
6. 代码示例
6.1 文字编辑器字符对象
java">// 抽象享元
interface Character {
void display(int x, int y, String color);
}
// 具体享元
class ConcreteCharacter implements Character {
private char symbol; // 内部状态
public ConcreteCharacter(char symbol) {
this.symbol = symbol;
}
@Override
public void display(int x, int y, String color) { // 外部状态
System.out.printf("显示字符 %c 在(%d,%d),颜色:%s%n", symbol, x, y, color);
}
}
// 享元工厂
class CharacterFactory {
private Map<Character, Character> pool = new HashMap<>();
public Character getCharacter(char key) {
if (!pool.containsKey(key)) {
pool.put(key, new ConcreteCharacter(key));
}
return pool.get(key);
}
}
// 客户端
public class Client {
public static void main(String[] args) {
CharacterFactory factory = new CharacterFactory();
List<Character> document = new ArrayList<>();
document.add(factory.getCharacter('H'));
document.add(factory.getCharacter('e'));
document.add(factory.getCharacter('l'));
document.add(factory.getCharacter('l'));
document.add(factory.getCharacter('o'));
int x = 0;
for (Character c : document) {
c.display(x++, 0, "black"); // 传递外部状态
}
}
}
7. 模式变种
- 复合享元模式:将多个单纯享元组合成复合对象
- 线程级享元:结合ThreadLocal实现线程级共享
- 带缓存清理机制:实现LRU缓存策略管理享元对象
8. 优缺点分析
✔️ 优点:
- 极大减少内存中对象的数量
- 外部状态相对独立,增强程序灵活性
- 享元对象可被不同场景复用
❌ 缺点:
- 增加系统复杂度(需要分离内外状态)
- 需要额外关注线程安全问题
- 可能影响程序执行效率(查找/计算外部状态)
9. 相关模式对比
模式 | 目的 | 关键区别 |
---|---|---|
单例模式 | 控制实例数量 | 享元可能有多个不同实例 |
原型模式 | 对象克隆 | 享元强调共享已有对象 |
对象池模式 | 重用对象 | 对象池管理临时对象,享元管理永久对象 |
10. 实际应用案例
- Java String常量池(-128~127的Integer缓存)
- 棋牌游戏的棋子对象管理
- 文档编辑器中的字符/段落格式
- 图形编辑器中的图形对象
- 数据库连接池
11. 最佳实践建议
- 严格区分内部状态和外部状态
- 使用不可变对象作为享元对象
- 对享元工厂实现缓存淘汰策略(LRU)
- 考虑使用WeakReference管理享元池
- 外部状态参数尽量使用基本类型或不可变对象
- 多线程环境下需要同步处理享元工厂
🧩 记忆技巧:把享元模式想象成"共享单车"系统,单车(内部状态)是共享的,用户(外部状态)各自的使用位置不同。