享元模式

享元模式

一、享元模式是什么?

享元设计模式(FlyWeight Design Pattern):通过共享已有实例,来避免创建新的实例,减少空间消耗,提高资源利用率

享元,意思就是共享的对象、单元、元素等。

主要特点:

  • 相同实例对象只保留一份,即享元对象
  • 需要某个实例时,尽量共用享元对象
  • 享元对象可以减少对象的数量

享元模式的本质就是缓存对象,减少资源消耗。

二、为什么要用享元模式?

享元模式,目的就是为了共享对象、减少资源的消耗。

使用享元模式,为的就是减少资源消耗、降低内存,提高资源利用率。

三、该如何用享元模式?

使用享元模式,需要注意几点:

  • 享元对象应该是不可变的,即没有setter方法
  • 享元对象会对所有地方都有影响
  • 享元对象中不应该存在可变的信息,即有可能发生变化的内容
  • 享元对象不应该被垃圾回收
  • 享元对象的内部状态是不可变的,外部转台是可变的

比如,数据库连接池,用户名、密码、url这些属于享元对象的内部状态,不可变;

连接可用标记等属性,属于外部状态,是可变的,比如回收连接后,可用标记会设为true。

享元模式的几个角色包括:

  • 抽象享元角色(FlyWeight):表示享元类的接口
  • 具体享元角色(ConcreteFlyWeight):表示被共享的享元实例对象
  • 非享元角色(Unsharable Flyweight):表示外部状态,区别于享元对象的内部状态,以参数形式注入享元对象
  • 享元工厂(FlyWeightFactory):负责生成和管理享元对象的工厂

享元模式结构:

享元模式结构

示例程序结构:

享元模式结构

享元类:

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
public class BigChar {

/* 字符名称 */
private char charName;
/* 字体字符串 */
private String fontData;

public BigChar(char charName) {
this.charName = charName;
loadCharData();
}

private void loadCharData() {
String fileDir = "src/com/pattern/flyweight/numbers/";
String fileName = fileDir + "big" + charName + ".txt";
try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
String line;
StringBuffer buf = new StringBuffer();
while ((line = reader.readLine()) != null) {
buf.append(line).append("\n");
}
fontData = buf.toString();
} catch (IOException e) {
e.printStackTrace();
}
}
}

享元工厂:

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
public class BigCharFactory {

private static BigCharFactory instance = new BigCharFactory();

/* 享元对象池 */
private Map<String, BigChar> pool = new HashMap<>();

private BigCharFactory(){}

public static BigCharFactory getInstance() {
return instance;
}

/**
* 获取享元对象接口
*/
public BigChar getChar(char charName) {
BigChar bigChar = pool.get("" + charName);
if (bigChar == null) {
bigChar = new BigChar(charName);
pool.put("" + charName, bigChar);
}
return bigChar;
}

}

享元使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class BigString {

private BigChar[] chars;

public BigString(String string) {
loadString(string);
}

private void loadString(String string) {
chars = new BigChar[string.length()];
BigCharFactory factory = BigCharFactory.getInstance();
for (int i = 0; i < string.length(); i++) {
char charName = string.charAt(i);
chars[i] = factory.getChar(charName);
}
}

}

四、享元模式有什么优缺点?

优点:

  • 共享相同对象,资源消耗低、利用率高
  • 对象数量少,方便管理享元对象

缺点:

  • 需要将共享对象分离出来,可能会使得程序变得复杂

五、享元和单例、缓存、对象池的区别

5.1 享元 vs. 单例

  • 单例本质是为了控制实例的数量,重点在于数量。

  • 享元关注的点是实例共享,重点在于共享,数量不是主要矛盾。

5.2 享元 vs. 缓存

  • 缓存的作用是为了提高查询速度,目的在于提高效率。

  • 享元的目标是为了降低资源消耗,主要关注点是资源的利用率。

5.3 享元 vs. 对象池

  • 对象池主要用于管理对象,重点在于对一批对象进行管理分配回收。

  • 享元主要还是为了对象的复用,在管理方面不是特别关注。

六、享元模式的实际应用

Integer:

  • Java 中 Integer 默认缓存 -128 ~ 127 之间的数字对象
  • Integer 的缓存对象是一开始就创建好的

String:

  • Java 中 String 对象会缓存到字符串常量池中,其他地方可以直接引用
  • String 的常量池对象是动态创建的
作者

jiaduo

发布于

2022-01-15

更新于

2023-04-03

许可协议