概念
运用共享技术有效地支持大量的细粒度的对象(flyweight)。通过对对象的共享(重复使用),避免创建过多对象。因为过多的对象将损耗应用的性能,甚至是造成内存泄露。
共享对象的体系中,也可以有不能共享的子类。
由于对象是共享的,所以用户不能对对象进行实例化,因此需要建立一个工厂类单独为用户提供共享的对象。
内部状态与外部状态
对于共享的对象,关键在于将它的属性分为两个部分:内部状态与外部状态。内部状态指的是存储于flyweight中,不随场景的改变而改变;外部状态取决于使用flyweight的场景,并根据场景的变化而变化,不可共享,用户在使用时必须为对象提供外部状态。
比如要为一篇txt文档中的每一个字符建立一个对象,则对象太多,容易oom,可以采用享元模式。为字符表中的每一个字符建立一个对象(可以认为是字母,如果是汉字的话就算了),这个字符就是该flyweight的内部状态,字符显示的位置就是外部状态。
关键词
共享——对象重复使用,避免不同的环境下大量创建对象。
使用场景
必须同时满足下列要求才可以使用享元模式。
1,系统内部使用了大量的对象,造成了内存的浪费。
2,剔除对象内部的外部状态后,可以使用较少的对象代替原来大量的对象,即能明显地降低对象的数量。
享元工厂
由于对象需要重复使用,因此必须提供一个获取对象的工厂类——不能让使用者自己new,因为使用者new的时候并不能保证该对象可以重复使用——这个工厂类就是享元工厂,它负责给客户端返回对象,并将该对象缓存起来。
比如有一个bean(来源于设计模式之禅),它结构如下:
<span style="font-size:18px;">public class Person {
private String subject;//科目
private String code;//准考证号
private String name;//考生姓名
private String location;//考试地点
}</span>
对于科目与考试地点来说,它的数目是有限的。因此科目+考试地点可以组成一个Person对象的可共享部分,可以将这两部分相同的对象进行复用。而name,code等属性需要获取对象之后重新赋值。因此,工厂的示例如下:
<span style="font-size:18px;"> private static Map<String,Person> mPersons = new HashMap<>();
public static Person getPerson(String location,String subject){
String key = location+"-"+subject;
if(mPersons.containsKey(key)) {//如果有对象,则使用旧对象
return mPersons.get(key);
}else{//如果没有,则new一个
Person p = new Person();
p.setLocation(location);
p.setSubject(subject);
mPersons.put(key,p);
return p;
}
}</span>
多线程
总结
享元模式实质就是对已使用过的对象的重复利用,它使我们使用到了共享技术,为了方便对这些对象的创建与管理,使用一个工厂类专门用于获取对象(无论这个对象是new出来的,还是从旧有对象中复用的)。
而对象的复用,可以根据对象中一部分属性(比如上面的location与subject)生成一个key值,该key值就是对象的唯一标识。而有些对象的属性都是不可共享的(如Message),那么根本不需要生成key值,只需要使用List集合将所有的对象存储起来,每一次获取时直接从list中获取即可。