享元模式

定义

运用共享技术来有效地支持大量细粒度对象的复用;
它通过共享已经存在的对象来大幅度减少需要创建的对象数量、避免大量相似类的开销,从而提高系统资源的利用率。

适用场景

  • 常常应用于系统底层的开发,以便解决系统的性能问题;
  • 系统有大量相似对象、需要缓冲池的场景。

优点和缺点

优点

  • 减少对象的创建,降低内存中对象的数量,降低系统的内存,提高效率;
  • 减少内存之外的其他资源占用。

缺点

  • 关注内/外部状态、关注线程安全问题
  • 使系统、程序逻辑复杂化

结构

享元模式的定义提出了两个要求,细粒度和共享对象。因为要求细粒度,所以不可避免地会使对象数量多且性质相似,此时我们就将这些对象的信息分为两个部分:内部状态外部状态

  • 内部状态: 指对象共享出来的信息,存储在享元信息内部,并且不会随环境的改变而改变。
  • 外部状态: 指对象得以依赖的一个标记,随环境的改变而改变,不可共享。

主要角色:
抽象享元角色: 是所有的具体享元类的基类,为具体享元规范要求实现的公共接口,非享元的外部状态以参数的形式通过方法传入;
具体享元角色:实现抽象享元角色中所规定的接口;
非享元角色: 是不可共享的外部状态,它以参数的形式注入具体享元的相关方法中;
享元工厂角色: 负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检查系统中是否存在符合要求的享元对象,如果存在则提供给客户,如果不存在的话,则创建一个新的享元对象。

代码实现

我们在工作中,经常有一些需求文档,设计文档,接口文档等等,其实每次的文档格式是基本一样的只是内容不同,所以我们可以把文档模板做成享元对象,只需要每次在里面填充不同的内容即可

首先我们创建非享元角色,文档内容,就算模板相同,每个模板的具体内容也是不同,属于外部状态

1
2
3
4
5
6
7
8
9
10
11
12
public class DocContent {

private String content;

public DocContent(String content) {
this.content = content;
}

public String getContent() {
return content;
}
}

创建抽象享元角色

1
2
3
public interface Template {
void createDoc(DocContent content);
}

具体享元角色,其中的templateName字段就是内部状态,每个模板的模板名称是相同的

1
2
3
4
5
6
7
8
9
10
11
12
13
public class DocTemplate implements Template{

private String templateName;

public DocTemplate(String templateName) {
this.templateName = templateName;
}

@Override
public void createDoc(DocContent content) {
System.out.println("当前模板是:"+templateName+" 内容是:"+content.getContent());
}
}

创建享元工厂,这里是使用HashMap来做享元对象的存储,HashMap属于非线程安全的,所以如果是在并发的情况下,可能会有并发问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
public class DocFactory {

private final Map<String,DocTemplate> TEMPLATE_MAP = new HashMap<>();

public Template getTemplate(String templateName){
if (!TEMPLATE_MAP.containsKey(templateName)){
System.out.print("新建模板:"+templateName+" ");
TEMPLATE_MAP.put(templateName,new DocTemplate(templateName));
}
return TEMPLATE_MAP.get(templateName);
}

}

具体调用,这里使用随机数连续10次调用来查看输出内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Test {

private static final String[] templateNames = {"需求文档","接口文档","设计文档","简历"};

public static void main(String[] args) {
DocFactory factory = new DocFactory();

for (int i = 0; i < 10; i++) {
String templateName = templateNames[(int) (Math.random() * templateNames.length)];
Template template = factory.getTemplate(templateName);
template.createDoc(new DocContent(templateName+"的内容是"+i+"....."));
}
}
}

输出结果,从结果中可以看出,每一种模板在第一次使用的时候会创建新的,但是后续再次调用的时候就不会再新创建对象,而模板的内容每次调用都是不一样的

1
2
3
4
5
6
7
8
9
10
新建模板:  接口文档   当前模板是:  接口文档  内容是:  接口文档的内容是0.....
新建模板: 设计文档 当前模板是: 设计文档 内容是: 设计文档的内容是1.....
新建模板: 需求文档 当前模板是: 需求文档 内容是: 需求文档的内容是2.....
当前模板是: 设计文档 内容是: 设计文档的内容是3.....
当前模板是: 设计文档 内容是: 设计文档的内容是4.....
当前模板是: 接口文档 内容是: 接口文档的内容是5.....
新建模板: 简历 当前模板是: 简历 内容是: 简历的内容是6.....
当前模板是: 接口文档 内容是: 接口文档的内容是7.....
当前模板是: 设计文档 内容是: 设计文档的内容是8.....
当前模板是: 设计文档 内容是: 设计文档的内容是9.....

UML类图