说明
享元模式用来尽可能减少内存使用量以及分享资讯给尽可能多的相似物件;它适合用于只是因重复而导致使用无法令人接受的大量内存的大量物件。通常物件中的部分状态是可以分享。
UML
两个状态:
内蕴状态存储在享元内部,不会随环境的改变而有所不同,是可以共享的。
外蕴状态是不可以共享的,它随环境的改变而改变的,因此外蕴状态是由客户端来保持(因为环境的变化是由客户端引起的)。
角色:
抽象享元角色:为具体享元角色规定了必须实现的方法,而外蕴状态就是以参数的形式通过此方法传入。在Java中可以由抽象类、接口来担当。
具体享元角色:实现抽象角色规定的方法。如果存在内蕴状态,就负责为内蕴状态提供存储空间。
享元工厂角色:负责创建和管理享元角色。要想达到共享的目的,这个角色的实现是关键!
客户端角色:维护对所有享元对象的引用,而且还需要存储对应的外蕴状态。
代码
一个字符管理的小示例,先看示例,最后再结合上述定义做解释。
抽象享元角色,字符
/**
* @author ctl
* @date 2021/1/22
*/
public interface Word {
void write(int x, int y);
}
具体享元角色,中文字符
/**
* @author ctl
* @date 2021/1/22
*/
public class ChineseWord implements Word {
private String str;
public ChineseWord(String str) {
this.str = str;
}
@Override
public void write(int x, int y) {
System.out.println("写中文:" + str + ",坐标[" + x + "," + y + "]");
}
}
享元工厂角色,管理字符对象
/**
* @author ctl
* @date 2021/1/22
*/
public class WordFactory {
private static Map<String, Word> map = new HashMap<>();
public static Word getWord(String str) {
Word word = map.get(str);
if (word == null) {
word = new ChineseWord(str);
map.put(str, word);
}
return word;
}
}
客户端
/**
* @author ctl
* @date 2021/1/22
* 两个word对象是同一个对象,但是坐标不同
*/
public class FlyWeightMain {
public static void main(String[] args) {
Word test = WordFactory.getWord("测试");
Word test2 = WordFactory.getWord("测试");
test.write(1, 2);
test2.write(3, 4);
System.out.println(test == test2);
}
}
结果
可以看出两个word对象的内存地址相同,没有开辟新的内存,同时可以满足外部控制坐标不同的需求。
我们再来看一下两个状态和四个角色在上述示例中的体现。
内蕴状态对应这个字符对象的写方法中的类型,示例中是中文,是固定的,可共享的,外部不可变的。
外蕴状态对应这个字符对象的坐标,是不共享的,是外部可变的。
抽象享元角色对应Word接口,定义了写方法。
具体享元角色对应中文字符类,实现类写方法,并存储了内蕴状态。
享元工厂角色对应工厂类,达到了共享对象的目的。
客户端角色对应测试类,可以传入外蕴状态。
总结
如果一个应用程序使用了大量的对象,而这些对象造成了很大的存储开销的时候就可以考虑是否可以使用享元模式。
例如,如果发现某个对象的生成了大量细粒度的实例,并且这些实例除了几个参数外基本是相同的,如果把那些共享参数移到类外面,在方法调用时将他们传递进来,就可以通过共享大幅度单个实例的数目。
数据库连接池中体现了享元模式的思想,使用同一目标的连接对象时,不会每次创建新的连接,而是从池中去取。