设计模式之享元模式
介绍享元模式之前,来聊聊Java中的String类型特性:String类型的
1、对象一旦被创造就不可改变;
2、当两个String对象所包含的内容相同的时候,JVM只会创建一个String对象对应着两个不同的对象引用。
验证一下2的特性
1 public class TestPattern{
2 public static void main(String[] args){
3 String s1 = "SimbaChen";
4 String s2 = "SimbaChen";
5 System.out.println(s1 == s2);
6 }
7 }
这段代码会告诉你 s1==s2 是true,这就说明在JVM中s1和s2两个引用了同一个String对象。
现在验证一下1的特性,在输出之前加入代码:
s2 = s2 + "is a sb";
这时候 s1==s2 结果为false,这是因为JVM在添加语句后,创建了一个新的String对象,而不是修改String对象的内容。
这就是享元模式的设计的优势:避免创建大量对象产生的不必要的资源损耗。那么享元模式的使用时怎么样的呢?
享元模式的定义是:采用一个共享类来避免大量拥有相同内容的“小类”的开销。这种开销一般是指的内存开销。
如何来实现共享呢?很多事物间相同的仅仅是部分,如果只有完全相同的事物才能共享,那么享元模式可以说是不可行的,因此应该尽量将事物的共性共享,同时保留其个性。为了做到这个要求,享元模式中有了内蕴状态(共性)和外蕴状态(个性)的区分。内蕴状态存储在享元内部,不会随环境的改变而有所不同,是可以共享的。外蕴状态是不可以共享的,它随环境的改变而改变的,因此外蕴状态是由客户端来保持(因为环境的变化是由客户端引起的)。在每个具体的环境下,客户端将外蕴状态传递给享元,从而创建不同的对象出来。
根据《Java与模式》的分法,享元模式可分为:单纯享元模式 和 复合享元模式。
单纯享元模式的结构如下:
1、抽象享元角色:为具体享元角色规定了必须实现的方法,而外蕴状态就是以参数的形式通过此方法传入;
2、具体享元角色:实现抽象角色规定的方法。如果存在内蕴状态,就负责为内蕴状态提供存储空间。
3、享元工厂角色:负责创建和管理享元角色。要想达到共享的目的,这个角色的实现是关键;
4、客户端角色:维护对所有享元对象的引用,而且还需要存储对应的外蕴状态。
复合享元模式的结构如下:
1、抽象享元角色:为具体享元角色规定了必须实现的方法,而外蕴状态就是以参数的形式通过此方法传入。在Java 中可以由抽象类、接口来担当;
2、具体享元角色:实现抽象角色规定的方法。如果存在内蕴状态,就负责为内蕴状态提供存储空间;
3、复合享元角色:它所代表的对象是不可以共享的,并且可以分解成为多个单纯享元对象的组合;
4、享元工厂角色:负责创建和管理享元角色。要想达到共享的目的,这个角色的实现是关键;
5、客户端角色:维护对所有享元对象的引用,而且还需要存储对应的外蕴状态。
统比一下单纯享元对象和复合享元对象,里面只多出了一个复合享元角色,但是它的结构就发生了很大的变化。复合享元模式采用了组合模式——为了将具体享元角色和复合享元角色同等对待和处理。这也就决定了复合享元角色中所包含的每个单纯享元都具有相同的外蕴状态,而这些单纯享元的内蕴状态可以是不同的。
设想一下有一个含有多个属性的对象,要被创建一百万次,并使用它们。这时候正是使用享元模式的好时机。我们以例子来说明:
1 //这便是使用了静态属性来达到共享
2 //它使用了数组来存放不同客户对象要求的属性值
3 //它相当于享元角色(抽象角色被省略了)
4 class ExternalizedData {
5 static final int size = 5000000;
6 static int[] id = new int[size];
7 static int[] i = new int[size];
8 static float[] f = new float[size];
9 static {
10 for(int i = 0; i < size; i++)
11 id[i] = i;
12 }
13 }
14
15 //这个类仅仅是为了给ExternalizedData 的静态属性赋值、取值
16 //这个充当享元工厂角色
17 class FlyPoint {
18 private FlyPoint() {}
19 public static int getI(int obnum) {
20 return ExternalizedData.i[obnum];
21 }
22 public static void setI(int obnum, int i) {
23 ExternalizedData.i[obnum] = i;
24 }
25 public static float getF(int obnum) {
26 return ExternalizedData.f[obnum];
27 }
28 public static void setF(int obnum, float f) {
29 ExternalizedData.f[obnum] = f;
30 }
31 public static String str(int obnum) {
32 return "id: " + ExternalizedData.id[obnum] + ", i = " + ExternalizedData.i[obnum] + ", f = " + ExternalizedData.f[obnum];
33 }
34 }
35
36 //客户程序
37 public class FlyWeightObjects {
38 public static void main(String[] args) {
39 for(int i = 0; i < ExternalizedData.size; i++) {
40 FlyPoint.setI(i, FlyPoint.getI(i) + 1);
41 FlyPoint.setF(i, 47.0f);
42 }
43 System.out.println(FlyPoint.str(ExternalizedData.size -1));
44 }
45 }
使用优缺点
享元模式优点就在于它能够大幅度的降低内存中对象的数量;而为了做到这一步也带来了它的缺点:它使得系统逻辑复杂化,而且在一定程度上外蕴状态影响了系统的速度。所以一定要切记使用享元模式的条件:
1)系统中有大量的对象,他们使系统的效率降低。
2)这些对象的状态可以分离出所需要的内外两部分。