设计模式之享元模式
享元模式应该怎么来实现?什么情况下会用到享元模式?有什么好处?
先来设计一个场景,读过大学的都知道,到了期末的时候,大家就要开始准备选择自己下学期的课程,以及老师。在这个时候由于选课的集中,以及访问选课页面的增多,从而会导致生成的对象增多,此时的内存占用率肯定会暴增,如果学校的服务器内存不够,那么很可能会出现OOM的情况。
下面简单模拟一下创建课程对象:
public class FlyWeight_01 {
//课程类
static class Course{
private Integer id;//课程ID
private String courseName;//课程名称
private String teacher;//授课老师
private String address;//授课地址
public Course(Integer id, String courseName, String teacher, String address) {
this.id = id;
this.courseName = courseName;
this.teacher = teacher;
this.address = address;
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getCourseName() {
return courseName;
}
public void setCourseName(String courseName) {
this.courseName = courseName;
}
public String getTeacher() {
return teacher;
}
public void setTeacher(String teacher) {
this.teacher = teacher;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
public static void main(String[] args) {
//物理课-张老师
Course physicsCourse1 = new Course(1,"物理课","张老师", "A-1");
//物理课-李老师
Course physicsCourse2 = new Course(1,"物理课","李老师", "A-2");
//高数课-赵老师
Course mathCourse1 = new Course(2,"高数课","赵老师", "B-1");
//高数课-蒙老师
Course mathCourse2 = new Course(2,"高数课","蒙老师", "B-2");
}
}
同一门课程,对应了不同的老师,以及不同的教师,如果采用以上创建对象的方式来频繁创建选课列表,那么就会导致对象重复创建和对象的数量的激增,从而使得内存占用率迅速上升以至于出现OOM。
按理来讲,这么简单的一个创建对象的功能是不应该导致OOM,但是偏偏就出现了,既然出现了的话,那么就应该想办法来解决。
而解决的办法就是对象的重复利用,所谓的重复利用就是同一个课程、同一个老师、同一个上课地址的对象,我们只需要创建一次即可,如果其他同学也要选择改老师的课程,只需要将该对象提供即可,不必再重新创建。
那到底应该怎么来做呢:
- 首先要做的就是将课程中相同的属性全部抽离出来,单独形成一个类或抽象类(这部分属性值在初次设值之后就不应该再发生改变);
- 其次必须要定义一个指向该类实例的唯一标志;
- 最后需要有一个容器来保存已经创建过的对象,同时还必须有一个唯一标志指向该对象(因为必须保证通过该标志取到的对象始终都是同一个)
如下代码:
public class FlyWeight_02 {
//抽象课程类
static abstract class AbstractCourse{
private Integer id;//课程ID
private String courseName;//课程名称
private String teacher;//授课老师
private String address;//授课地址
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getCourseName() {
return courseName;
}
public void setCourseName(String courseName) {
this.courseName = courseName;
}
public String getTeacher() {
return teacher;
}
public void setTeacher(String teacher) {
this.teacher = teacher;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
//具体课程类
static class ConcreteCourse extends AbstractCourse{
private final String key;//用final修饰,保证key不会被改变
public ConcreteCourse(String key) {
this.key = key;
}
}
//工厂方法模式创建课程
static class CourseFactory{
private static Map<String, AbstractCourse> courseMap = new HashMap<>();
public static AbstractCourse createCourse(String key){
if(!courseMap.containsKey(key)){
AbstractCourse course = new ConcreteCourse(key);
//初始化,设置参数
String[] arr = key.split("_");
course.setId(Integer.valueOf(arr[0]));
course.setCourseName(arr[1]);
course.setTeacher(arr[2]);
course.setAddress(arr[3]);
courseMap.put(key, course);
}
return courseMap.get(key);
}
}
public static void main(String[] args) {
AbstractCourse course = CourseFactory.createCourse("1_物理课_张老师_A-1");
AbstractCourse course1 = CourseFactory.createCourse("1_物理课_李老师_A-2");
AbstractCourse course2 = CourseFactory.createCourse("2_高数课_赵老师_B-1");
AbstractCourse course3 = CourseFactory.createCourse("2_高数课_蒙老师_B-2");
//再次获取张老师的物理课,对比是否是同一个对象
AbstractCourse course01 = CourseFactory.createCourse("1_物理课_张老师_A-1");
System.out.println(course01==course);
System.out.println(course01.equals(course));
}
}
打印结果:
true
true
由此可以说明,第一次创建的对象得到了重复利用,这也就是享元模式。
下面来看看享元模式的定义:使用共享对象可有效的支持大量的细粒度的对象。
细粒度对象,就是将对象分为两部分,一部分是外部状态,一部分是内部状态;
内部状态,是不随环境变化而变化的,也就是固定的;
外部状态,随环境变化而变化的,如创建时传入的key。
享元模式主要应用池化技术,如:线程池,避免线程的重复创建带来的性能消耗,且可以统一管理。