享元模式(Flyweight Pattern)是一种结构模式(Structural Design Pattern)。它的设计初衷是为了节省内存开销。在面向对象程序设计中,所有的数据都包含在对象中。在某些场景下,有些数据是高度重复的。当使用这些重复数据时,如果每次都重新创建一个对象的话,那么,这种方式“浪费”了许多内存空间,以及“浪费”了CPU资源来反复的创建和销毁这些对象。
一个更加有效的使用这些重复数据的方式是将其设置为共享数据。这些共享数据有两个显著的特点。其一,共享数据是不会变化的。如果这些数据可能发生变化的话,这些数据是不能共享的。其二,系统中只有一份共享数据。在需要使用这些数据的地方都使用这一份共享的数据。
在享元模式中有着4个参与方。
图一 享元模式结构。
我们将以在校本科学生和所完成的课程为例,展示享元模式的使用方法。首先,我们先定义享元抽象类CompletedCourses。该类用于表示一名学生已完成的课程。
import java.util.List;
public abstract class CompletedCourses {
private List<String> completedCourses = null;
public CompletedCourses(List<String> aCourseList) {
this.completedCourses = aCourseList;
}
}
然后,我们定义表示大一至大四学生所完成课程的类。CoursesCompletedByFreshman、CoursesCompletedBySophomore、CoursesCompletedByJunior和CoursesCompletedBySenior分别表示大一、大二、大三和大四学生所完成的课程。
import java.util.List;
import java.util.Collections;
import java.util.Arrays;
public class CoursesCompletedByFreshman extends CompletedCourses {
public CoursesCompletedByFreshman() {
// 大一新生还没有完成任何课程
super(Collections.EMPTY_LIST);
}
}
public class CoursesCompletedBySophomore extends CompletedCourses {
public CoursesCompletedBySophomore() {
// 假设大二学生已完成大一课程"Introduction to Computer Science"
super(Arrays.asList("Introduction to Computer Science"));
}
}
public class CoursesCompletedByJunior extends CompletedCourses {
public CoursesCompletedByJunior() {
// 假设大三学生已完成大一课程和大二课程
super(Arrays.asList("Introduction to Computer Science", "Data Structures"));
}
}
public class CoursesCompletedBySenior extends CompletedCourses {
// 假设大三学生已完成大一课程、大二课程和大三课程
public CoursesCompletedBySenior() {
super(Arrays.asList(
"Introduction to Computer Science",
"Data Structures",
"Operating Systems"));
}
}
然后,我们使用类Student来表示一名在校本科生。其中,成员变量用于表示该学生已完成的课程列表。我们在main()函数中创建了三名大一学生Adam, David和Amy,和一名大二学生Julie。
public class Student {
private String name = null;
private CompletedCourses courses = null;
public Student(String name, CompletedCourses courses) {
this.name = name;
this.courses = courses;
}
public static void main(String[] args) {
CompletedCourseFactory factory = new CompletedCourseFactory();
// 创建大一学生Adam
Student adam = new Student("Adam", factory.make("freshman"));
// 创建大一学生David
Student david = new Student("David", factory.make("freshman"));
// 创建大一学生Amy
Student amy = new Student("Amy", factory.make("freshman"));
// 创建大二学生Julie
Student julie = new Student("Julie", factory.make("sophomore"));
}
}
最后,我们再来看看如何实现CompletedCourseFactory工厂类。该工厂类定义了一个静态成员变量sharedCompletedCourses用于存放共享的对象。每当需要创建一个新对象时,该工厂类会首先在共享池中查找是否已存在。如果已存在,则直接返回该共享对象。如果不存在,则创建一个新对象,并将其添加到共享池中。因此,在整个应用程序中,我们只会创建出四个对象,分别表示大一至大四各阶段所完成的课程列表。
import java.util.Map;
import java.util.HashMap;
public class CompletedCourseFactory {
private static Map<String, CompletedCourses> sharedCompletedCourses = new HashMap<>();
public CompletedCourses make(String year) {
CompletedCourses courseList = sharedCompletedCourses.get(year);
if (courseList != null) {
// 如果已存在共享池中,则直接返回该共享对象。
return courseList;
}
// 否则创建一个新对象
switch (year) {
"freshman":
courseList = new CoursesCompletedByFreshman();
break;
"sophomore":
courseList = new CoursesCompletedBySophomore();
break;
"junior":
courseList = new CoursesCompletedByJunior();
break;
"senior":
courseList = new CourseCompletedBySenior();
break;
default:
// should not reach here
throw new IllegalArgumentException("Unsupported year.");
}
sharedCompletedCourses.add(year, courseList);
return courseList;
}
}
Java标准库中也使用了享元模式。在Integer类中缓存了常用的Integer对象。从下面的实现来看,Integer类中实现了一个内部类IntegerCache,用于表示缓存的Integer对象。IntegerCache类中成员变量low和high指出了缓存整数数值的范围;cache则是这些缓存整数的数组。
当Integer.valueOf()函数被调用时,如果传入参数的值在缓存数据的数值范围内时,会返回已缓存的Integer对象。
// OpenJDK 15
package java.lang;
public final class Integer extends Number implements Comparable<Integer> {
...
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
}
...
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
...
}
在Java虚拟机的实现中,也使用了对象共享的思想,只不过它们的实现方式可能不同。例如,class对象是一种共享对象。当程序创建出两个不同的对象,并获取它们的class对象时,实际上,它们的class对象是同一个共享对象。这样做的原因是为了避免生成过多的重复对象,其思想与享元模式相同。
public class SharedObjectExample {
public static void main(String[] args) {
SharedObjectExample obj1 = new SharedObjectExample();
SharedObjectExample obj2 = new SharedObjectExample();
// 使用 == 操作符比较它们是否指向同一个对象
if (obj1.getClass() == obj2.getClass()) {
System.out.println("They share the class object.");
}
}
}
本章介绍了享元模式的结构和使用方法。享元模式的主要出发点是共享对象,以解决内存使用过多的问题。享元模式适用于当应用程序需要创建大量的对象,并且这些对象有着大量重复数据的场景。此时,可以将重复的数据抽象为一个共享的类。享元模式还可以和其他模式配合使用,使代码更高效。
注册用户登陆后可留言