设计模式之享元模式
定义
运用共享技术有效地支持大量细粒度的对象。如围棋中的黑白棋子,教室中的凳子和桌子,这些对象有很多相似的地方,
如果将其中相同的地方提取出来共享,就能节省大量的系统资源,这就是享元模式的适用场景。
结构
- Flyweight,享元接口,可以接收并作用于外部数据。
- ConcreteFlyweight,可以共享的享元实现对象,包含自己的内部状态。
- UnsharedConcreteFlyweight,非共享的享元实现对象,不能共享。
- FlyweightFactory,享元工厂,用来创建并管理共享的享元对象。
简单实现
享元接口
public interface Flyweight {
/**
* 传入外部状态
*/
void operation(String extrinsicState);
}
可共享的享元实现对象
public class ConcreteFlyweight implements Flyweight {
/**
* 内部状态
*/
private String intrinsicState;
public ConcreteFlyweight(String intrinsicState) {
this.intrinsicState = intrinsicState;
}
@Override
public void operation(String extrinsicState) {
System.out.println("共享对象的内部状态为:" + intrinsicState);
System.out.println("共享对象的外部状态为:" + extrinsicState);
}
}
不能共享的享元实现对象
public class UnsharedConcreteFlyweight implements Flyweight {
@Override
public void operation(String extrinsicState) {
System.out.println("非共享对象的外部状态为:" + extrinsicState);
}
}
享元工厂
import java.util.HashMap;
import java.util.Map;
public class FlyweightFactory {
private Map<String, Flyweight> flyweightMap = new HashMap<>();
public Flyweight getFlyweight(String key) {
if (!flyweightMap.containsKey(key)) {
flyweightMap.put(key, new ConcreteFlyweight(key));
}
return flyweightMap.get(key);
}
}
客户端
public class Client {
public static void main(String[] args) {
FlyweightFactory flyweightFactory = new FlyweightFactory();
Flyweight flyweight1 = flyweightFactory.getFlyweight("abc");
Flyweight flyweight2 = flyweightFactory.getFlyweight("abc");
System.out.println(flyweight1 == flyweight2);
flyweight1.operation("aaa");
flyweight1.operation("bbb");
}
}
在实际开发中,我们可以简化享元模式,不考虑非共享对象。
享元模式在JDK和Spring中的实现
Integer中valueOf()方法
public final class Integer extends Number implements Comparable<Integer> {
@HotSpotIntrinsicCandidate
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
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 integerCacheHighPropValue =
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() {}
}
Integer会缓存-128到127之间的数
Spring中的ReflectionUtils
public abstract class ReflectionUtils {
private static Method[] getDeclaredMethods(Class<?> clazz, boolean defensive) {
Assert.notNull(clazz, "Class must not be null");
Method[] result = declaredMethodsCache.get(clazz);
if (result == null) {
try {
Method[] declaredMethods = clazz.getDeclaredMethods();
List<Method> defaultMethods = findConcreteMethodsOnInterfaces(clazz);
if (defaultMethods != null) {
result = new Method[declaredMethods.length + defaultMethods.size()];
System.arraycopy(declaredMethods, 0, result, 0, declaredMethods.length);
int index = declaredMethods.length;
for (Method defaultMethod : defaultMethods) {
result[index] = defaultMethod;
index++;
}
}
else {
result = declaredMethods;
}
declaredMethodsCache.put(clazz, (result.length == 0 ? EMPTY_METHOD_ARRAY : result));
}
catch (Throwable ex) {
throw new IllegalStateException("Failed to introspect Class [" + clazz.getName() +
"] from ClassLoader [" + clazz.getClassLoader() + "]", ex);
}
}
return (result.length == 0 || !defensive) ? result : result.clone();
}
}
缓存一个class的所有方法,Spring中很多地方都使用到了缓存。上述两种示例都是享元模式的变形,只有内部状态,没有外部状态,也就不需要考虑非共享对象了。
总结
优点
- 减少对象数量,节省内存空间。
缺点
- 维护共享对象需要额外开销,如考虑共享对象何时失效,何时清除。
本质
享元模式的本质是分离和共享,分离的是对象中变和不变的部分,共享其中不变的部分。
使用场景
- 使用了大量的相似的细粒度对象,占用了大量内存。
参考
大战设计模式【19】—— 享元模式
大战设计模式(第二季)【6】———— 从源码看享元模式
设计模式(十二)——享元模式(Integer缓冲池源码分析)
设计模式的征途—12.享元(Flyweight)模式
享元模式(详解版)
研磨设计模式-书籍