目录
what什么是享元模式
Gof定义:运用共享技术有效地支持大量细粒度的对象。
HeadFirst定义:如何想让某个类的一个实例能用来提供许多“虚拟实例”,就使用享元模式。
系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式,它是一种对象结构型模式。
FlyWeight通常和Composite模式一起使用,用共享叶节点的DAG图来实现逻辑上的层次。
why为什么需要享元模式
有些应用程序得益于其在整个设计过程中采用对象技术,但简单化的实现代价极大。
例如,大多数文档编辑器都使用对象来表示嵌入的成分,例如表格和图形。通常文档编辑器会使用对象来表示内容,但这种设计的缺点在于代价很大,即使是一个中等大小的文档也需要几千个字符,这样会耗费大量内存。所以通常不受每个字符都用一个对象来表示。Flyweight的模式描述了如何共享对象,这样使得可以细粒度的使用它们。
说的有点抽象,这里我们想一下jvm常量池里面的对象,应该就比较明白。面向对象技术可以很好地解决一些灵活性或可扩展性问题,但在很多情况下需要在系统中增加类和对象的个数。当对象数量太多时,将导致运行代价过高,带来性能下降等问题。
how如何实现享元模式
享元模式的主要角色有如下。
- 抽象享元角色(Flyweight):是所有的具体享元类的基类,为具体享元规范需要实现的公共接口,非享元的外部状态以参数的形式通过方法传入。
- 具体享元(Concrete Flyweight)角色:实现抽象享元角色中所规定的接口。
- 非享元(Unsharable Flyweight)角色:是不可以共享的外部状态,它以参数的形式注入具体享元的相关方法中。
- 享元工厂(Flyweight Factory)角色:负责创建和管理享元角色。当客户对象请求一个享元对象时,享元工厂检査系统中是否存在符合要求的享元对象,如果存在则提供给客户;如果不存在的话,则创建一个新的享元对象。
下图是享元模式的结构图,其中:
- UnsharedConcreteFlyweight 是非享元角色,里面包含了非共享的外部状态信息 info;
- Flyweight 是抽象享元角色,里面包含了享元方法 operation(UnsharedConcreteFlyweight state),非享元的外部状态以参数的形式通过该方法传入;
- ConcreteFlyweight 是具体享元角色,包含了关键字 key,它实现了抽象享元接口;
- FlyweightFactory 是享元工厂角色,它是关键字 key 来管理具体享元;
- 客户角色通过享元工厂获取具体享元,并访问具体享元的相关方法。
开源框架经典案例
JDK源码之Integer类
比较经典的比如Integer类,很多同学在面试过程中会被问到,Integer可不可以用==号进行判断,这个面试官就是想考你知不知道其内部实现,只要看过源码就知道,Integer.valueOf()
这个静态工厂方法Integer
实例时,当传入的int
范围在-128
~+127
之间时,会直接返回缓存的Integer
实例,因为反复创建同一个值相同的包装类型是没有必要的。而为什么是-128到+127,这里面缓存范围,可以通过XX:AutoBoxCacheMax改变最大值,但是不能改变最小值。
/**
缓存范围,可以通过XX:AutoBoxCacheMax改变最大值,
* Cache to support the object identity semantics of autoboxing for values between
* -128 and 127 (inclusive) as required by JLS.
*
* The cache is initialized on first usage. The size of the cache
* may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.
* During VM initialization, java.lang.Integer.IntegerCache.high property
* may be set and saved in the private system properties in the
* sun.misc.VM class.
*/
private static class IntegerCache {
//最小值不会变
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropV alue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i, 127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int, ignore it.
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128, 127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
Guava缓存
熟悉guava的同学应该知道,guava有个AbstractLoadingCache类专门用来做缓存,同时支持自动过期清理,数据懒加载等等功能,很强大,因此其核心设计也是该设计模式。
看一下这个类的源码吧,其实核心方法很简单,首先通过CacheBuilder的build()方法加载缓存,再通过LoadingCachegetUnchecked便是去获取Key。哦,这里又有Builder模式了。
@Beta
public abstract class AbstractLoadingCache<K, V> extends AbstractCache<K, V> implements LoadingCache<K, V> {
protected AbstractLoadingCache() {
}
public V getUnchecked(K key) {
try {
return this.get(key);
} catch (ExecutionException var3) {
throw new UncheckedExecutionException(var3.getCause());
}
}
public ImmutableMap<K, V> getAll(Iterable<? extends K> keys) throws ExecutionException {
Map<K, V> result = Maps.newLinkedHashMap();
Iterator i$ = keys.iterator();
while(i$.hasNext()) {
K key = i$.next();
if (!result.containsKey(key)) {
result.put(key, this.get(key));
}
}
return ImmutableMap.copyOf(result);
}
public final V apply(K key) {
return this.getUnchecked(key);
}
public void refresh(K key) {
throw new UnsupportedOperationException();
}
}
使用场景
- 当一个系统里面有大量相同或者相似的额对象,由于这类对象的大量使用,造成内存的浪费可以使用
- 需要享元模式会新增一个共享对象池,而这需要耗费资源,因此只有重复使用时才能使用。
- 当一个类的许多实例可以被同一个方法控制的时候,就可以使用。
优缺点对比
优点
- 减少了运行时对象实例的个数,节省内存。
- 将许多虚拟对象集中管理。
缺点
- 一旦实现了,那么单个的逻辑实例无法拥有独立的行文,毕竟是共享的嘛。