0%

享元模式

享元模式(Flyweight Pattern):通过共享优化大量细粒度对象

享元模式是结构型设计模式的一种,核心思想是通过共享技术有效支持大量细粒度对象,减少内存占用和对象创建开销。它将对象的状态分为 “内部状态”(可共享、不变)和 “外部状态”(不可共享、可变),通过共享内部状态实现对象复用,本质是 “分离与共享”。

享元模式的核心结构

享元模式通过四个角色实现对象的共享与管理,分工明确:

享元接口(Flyweight)

  • 定义享元对象的公共接口,声明接收外部状态的方法。
  • 示例:Character接口(定义字符的显示方法,接收位置等外部状态)。

具体享元(ConcreteFlyweight)

  • 实现享元接口,封装内部状态(可共享的不变数据),并通过接口接收外部状态(可变数据)。
  • 示例:ConcreteCharacter(存储字符的字形等内部状态,接收坐标等外部状态)。

非共享享元(UnsharedConcreteFlyweight)

  • 不需要共享的享元对象,通常包含无法共享的外部状态,直接实例化无需缓存。
  • 示例:SpecialCharacter(特殊符号,使用频率低且状态独特,无需共享)。

享元工厂(FlyweightFactory)

  • 负责创建和管理享元对象,维护享元池(缓存已创建的享元),确保相同内部状态的对象只被创建一次。
  • 示例:CharacterFactory(缓存字符对象,根据字符值返回共享实例)。

核心概念:内部状态与外部状态

享元模式的关键是对对象状态的拆分:

  • 内部状态(Intrinsic State)
    • 定义:对象固有的、不变的状态,可被多个对象共享。
    • 存储位置:由享元对象自身持有。
    • 示例:字符的 “字形”(如 ‘A’ 的形状固定,可共享)。
  • 外部状态(Extrinsic State)
    • 定义:对象依赖的、可变的状态,随场景变化,不可共享。
    • 存储位置:由客户端持有,使用时传递给享元对象。
    • 示例:字符的 “位置”(如 ‘A’ 在页面中的 x、y 坐标,每次显示可能不同)。

代码实现示例

以 “文档编辑器中的字符显示” 为例:文档中大量重复的字符(如 ‘a’、’b’)可共享其字形(内部状态),但位置(外部状态)不同,通过享元模式减少字符对象的创建。

1. 享元接口与具体享元

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 1. 享元接口(字符)
interface Flyweight {
// 接收外部状态(位置)并执行操作(显示)
void display(int x, int y);
}

// 2. 具体享元(普通字符)
class ConcreteCharacter implements Flyweight {
// 内部状态:字符的值(如'a'、'b',可共享)
private char character;

public ConcreteCharacter(char character) {
this.character = character;
}

// 外部状态:坐标(x,y),由客户端传递
@Override
public void display(int x, int y) {
System.out.printf("显示字符 '%c' 在位置 (%d, %d)%n", character, x, y);
}
}

// 3. 非共享享元(特殊符号,无需共享)
class SpecialCharacter implements Flyweight {
// 非共享状态:特殊符号的样式(如颜色、大小,不可共享)
private String style;

public SpecialCharacter(String style) {
this.style = style;
}

@Override
public void display(int x, int y) {
System.out.printf("显示特殊符号(样式:%s)在位置 (%d, %d)%n", style, x, y);
}
}

2. 享元工厂

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 享元工厂:管理字符享元池
class CharacterFactory {
// 享元池:缓存内部状态相同的享元对象
private Map<Character, Flyweight> characterPool = new HashMap<>();

// 获取享元对象:若存在则返回,否则创建并缓存
public Flyweight getCharacter(char c) {
// 检查池中是否存在
if (!characterPool.containsKey(c)) {
// 不存在则创建新的具体享元
characterPool.put(c, new ConcreteCharacter(c));
System.out.printf("创建字符 '%c' 的享元对象%n", c);
}
return characterPool.get(c);
}
}

3. 客户端使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class FlyweightDemo {
public static void main(String[] args) {
// 创建享元工厂
CharacterFactory factory = new CharacterFactory();

// 多次获取相同字符,实际共享一个享元对象
Flyweight a1 = factory.getCharacter('a');
a1.display(10, 20); // 显示字符 'a' 在位置 (10, 20)

Flyweight a2 = factory.getCharacter('a');
a2.display(30, 40); // 显示字符 'a' 在位置 (30, 40)

// 验证a1和a2是否为同一对象(共享)
System.out.println("a1与a2是否为同一对象:" + (a1 == a2)); // true

// 使用非共享享元(特殊符号)
Flyweight special = new SpecialCharacter("红色加粗");
special.display(50, 60); // 显示特殊符号(样式:红色加粗)在位置 (50, 60)
}
}
输出结果
1
2
3
4
5
创建字符 'a' 的享元对象
显示字符 'a' 在位置 (10, 20)
显示字符 'a' 在位置 (30, 40)
a1与a2是否为同一对象:true
显示特殊符号(样式:红色加粗)在位置 (50, 60)

享元模式的核心优势

  1. 减少内存占用
    通过共享内部状态,避免大量重复对象的创建(如文档中重复的字符),大幅降低内存消耗。
  2. 提高性能
    减少对象创建和垃圾回收的开销,尤其适合需要频繁创建细粒度对象的场景(如游戏中的粒子效果、字体渲染)。
  3. 分离可变与不可变状态
    清晰区分内部状态(不变)和外部状态(可变),使系统设计更灵活,便于维护。

适用场景

  1. 存在大量细粒度对象
    当系统中存在大量相似对象(如文本中的字符、棋盘上的棋子),且这些对象的大部分状态可共享时。
  2. 对象的大部分状态可共享
    内部状态(不变)占比高,外部状态(可变)可通过参数传递,如:
    • 游戏:士兵、子弹等重复单位(共享模型、纹理,外部状态为位置、血量)。
    • 图形界面:按钮、图标等组件(共享样式,外部状态为位置、大小)。
    • 数据库连接池:连接对象共享连接信息(内部状态),外部状态为当前用户会话。
  3. 对象创建成本高
    若对象初始化耗时或占用资源多(如网络连接、大型模型),通过享元池复用可显著提升性能。

优缺点分析

优点

  • 资源优化:减少对象数量,降低内存和 CPU 消耗。
  • 灵活性:外部状态可动态调整,不影响内部状态的共享。
  • 可管理性:通过工厂统一管理享元对象,便于监控和控制。

缺点

  • 复杂度增加:需要拆分内部 / 外部状态,设计享元工厂,增加了系统理解难度。
  • 外部状态依赖:客户端需管理外部状态并传递给享元,若外部状态复杂,可能影响效率。
  • 线程安全风险:若外部状态管理不当(如共享外部状态),可能导致线程安全问题。

经典应用案例

  1. 字符串常量池(String Pool)
    Java 中Stringintern()方法通过享元模式实现:相同字符序列的字符串共享同一个对象(如"abc" == "abc"true)。
  2. 数据库连接池
    连接池缓存数据库连接对象(内部状态:URL、用户名、密码),外部状态为当前会话,避免频繁创建连接。
  3. 游戏中的粒子系统
    大量粒子(如火焰、雨滴)共享纹理和运动规则(内部状态),位置和生命周期为外部状态,通过享元复用粒子对象。
  4. 字体渲染引擎
    相同字符(如 ‘A’)共享字形数据(内部状态),在不同位置显示时传递坐标(外部状态)。

总结

享元模式通过拆分对象的内部状态(共享)和外部状态(可变),实现了大量细粒度对象的高效复用,核心是 “共享不变,传递可变”。它特别适合存在大量相似对象的场景,能显著优化内存和性能。但需注意平衡设计复杂度,避免过度拆分状态导致系统难以维护

欢迎关注我的其它发布渠道

表情 | 预览
快来做第一个评论的人吧~
Powered By Valine
v1.3.10