👉文章示例代码👈

定义

运用共享技术有效地支持大量细粒度的对象。

“享”即共享,“元”指的是元件,也就是小颗粒的东西。“享元”顾名思义就是共享小部件。

很多的系统程序包含大量的对象,但是这些对象绝大多数都是差不多的,除了一些极个别的属性外。

那么也就是说,在一个系统程序中有多个相同对象的时候,我们只需要共享一份就可以了,不必去实例化每一个对象。

内部状态与外部状态

享元模式是区分内部状态与外部状态的:

  • 内部状态:可以共享,在享元对象的内部,状态不会随着环境改变而改变
  • 外部状态:不可以共享,在享元对象的外部,状态会随着环境改变而改变

场景示例

笔者这里以衣服为例。将衣服的品牌作为内部属性共享,将衣服的大小、成分、价格作为外部属性使用。

创建衣服类

1
2
3
4
5
6
7
8
9
10
11
12
/**
* @author zhh
* @description 衣服类
* @date 2020-02-16 22:36
*/
public interface Clothes {

/**
* 衣服信息
*/
void info();
}

创建衬衣类

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
37
38
39
40
41
42
43
44
45
46
47
/**
* @author zhh
* @description 衬衣类
* @date 2020-02-16 22:37
*/
public class Shirt implements Clothes {

/**
* 品牌
*/
private String brand;

/**
* 大小
*/
private String size;

/**
* 成分
*/
private String component;

/**
* 价格
*/
private String price;

public Shirt(String brand) {
this.brand = brand;
}

public void setSize(String size) {
this.size = size;
}

public void setComponent(String component) {
this.component = component;
}

public void setPrice(String price) {
this.price = price;
}

public void info() {
System.out.println(String.format("当前衬衣信息: {品牌: %s, 大小: %s, 成分: %s, 价格: %s}", brand, size, component, price));
}
}

在该场景中,笔者将品牌属性作为其内部状态,在对象创建的初期就初始化完成,状态不会随着外部环境变化,可以共享。

而将大小、成分、价格这些属性当做外部状态,通过 setter 方法供外部修改,状态会随外部环境变化,这些属性不能够共享。

创建衬衣工厂类

享元模式中通常会出现工厂模式,需要创建一个享元工厂来负责维护一个享元池。

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
/**
* @author zhh
* @description 衣服工厂类
* @date 2020-02-16 22:48
*/
public class ClothesFactory {

private static final Map<String, Clothes> CLOTHES_MAP = new HashMap<String, Clothes>();

/**
* 获取衬衣
* @param brand 品牌
* @return
*/
public static Clothes getShirt(String brand) {
Shirt shirt = (Shirt) CLOTHES_MAP.get(brand);

if (shirt == null) {
shirt = new Shirt(brand);
CLOTHES_MAP.put(brand, shirt);
System.out.println("创建新的品牌衬衣, 品牌为: " + brand);
}

return shirt;
}
}

测试类及输出

方便起见,在该测试类中随机模拟数据。

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
/**
* @author zhh
* @description 测试类
* @date 2020-02-16 23:04
*/
public class Test {

private static final String[] BRAND = {"YOUNGOR雅戈尔", "FIRS杉杉", "ROMON罗蒙", "Hodo红豆"};

private static final String[] SIZE = {"S", "M", "L", "XL", "XXL"};

private static final String[] COMPONENT = {"棉", "涤纶", "真丝"};

private static final String[] PRICE = { "¥99", "¥129", "¥149", "¥199" };

/**
* 随机模拟
* @param strs 随机模拟数组
* @return
*/
public static String getRandom(String[] strs) {
return strs[new Random().nextInt(strs.length)];
}

public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
Shirt shirt = (Shirt) ClothesFactory.getShirt(getRandom(BRAND));
shirt.setSize(getRandom(SIZE));
shirt.setComponent(getRandom(COMPONENT));
shirt.setPrice(getRandom(PRICE));
shirt.info();
}
}
}

测试类的输出结果如下:

创建新的品牌衬衣, 品牌为: ROMON罗蒙
当前衬衣信息: {品牌: ROMON罗蒙, 大小: M, 成分: 真丝, 价格: ¥129}
创建新的品牌衬衣, 品牌为: FIRS杉杉
当前衬衣信息: {品牌: FIRS杉杉, 大小: S, 成分: 棉, 价格: ¥199}
当前衬衣信息: {品牌: ROMON罗蒙, 大小: M, 成分: 棉, 价格: ¥99}
创建新的品牌衬衣, 品牌为: Hodo红豆
当前衬衣信息: {品牌: Hodo红豆, 大小: M, 成分: 棉, 价格: ¥99}
当前衬衣信息: {品牌: FIRS杉杉, 大小: XL, 成分: 真丝, 价格: ¥99}
当前衬衣信息: {品牌: ROMON罗蒙, 大小: XXL, 成分: 真丝, 价格: ¥99}
当前衬衣信息: {品牌: Hodo红豆, 大小: M, 成分: 真丝, 价格: ¥99}
当前衬衣信息: {品牌: Hodo红豆, 大小: M, 成分: 涤纶, 价格: ¥149}
创建新的品牌衬衣, 品牌为: YOUNGOR雅戈尔
当前衬衣信息: {品牌: YOUNGOR雅戈尔, 大小: XXL, 成分: 真丝, 价格: ¥129}
当前衬衣信息: {品牌: ROMON罗蒙, 大小: XL, 成分: 涤纶, 价格: ¥199}

类结构图

以上示例类的结构图如下所示
image.png

总结

适用场景

  • 应用于系统底层开发,解决系统性能问题。
  • 系统有大量相似对象,需要缓冲池。

优点

  • 共享相同或者相似的细粒度对象,减少对象的创建,降低系统内存,提高效率。
  • 外部状态相对独立,对象可以在不同的环境中被复用。

缺点

  • 读取外部状态使得运行时间变长。
  • 内部状态与外部状态分离,使得程序的逻辑复杂化。

参考