1. 定义
享元模式(Flyweight Pattern):运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式,它是一种对象结构型模式。
共享对象,复用对象实例。
对象复用2. 设计
主要角色:
- 抽象享元类(Flyweight),声明享元公共方法,可以给外界获取内部状态,或者给享元设置外部状态。
- 具体享元类(Concrete Flyweight),实现享元抽象类,为内部享元提供存储空间。
- 非共享享元类(Unshared Concrete Flyweight),不能被共享的享元类。
- 享元工厂类(Flyweight Factory),创建并且管理享元对象。
类图:
享元模式-类图抽象享元类,定义所有享元的公共方法。
public interface IFlyweight {
void sayHello();
}
具体享元类,实现方法:
public class ConcreteFlyweight implements IFlyweight {
private String hello;
public ConcreteFlyweight(String hello) {
this.hello = hello;
}
public void sayHello() {
System.out.println(hello);
}
}
享元工厂,使用 Map 缓存享元,在创建享元的接口里,先走缓存获取,没有的话再创建。
public class FlyweightFactory {
private Map<String, IFlyweight> flyweightMap = new HashMap<String, IFlyweight>();
public IFlyweight getFlyweight(String key) {
IFlyweight flyweight = flyweightMap.get(key);
if (flyweight == null) {
flyweight = new ConcreteFlyweight(key);
flyweightMap.put(key, flyweight);
}
return flyweight;
}
}
使用地方:
public class TestFlyweight {
public static void main(String[] args) {
FlyweightFactory factory = new FlyweightFactory();
// 新对象
IFlyweight flyweight = factory.getFlyweight("sayHello");
flyweight.sayHello();
// 新对象
IFlyweight flyweight1 = factory.getFlyweight("你好");
flyweight1.sayHello();
// 对象复用
IFlyweight flyweight2 = factory.getFlyweight("sayHello");
flyweight2.sayHello();
System.out.println("复用:" + (flyweight == flyweight2));
// 不可复用对象
IFlyweight flyweight3 = new UnshareConcreteFlyweight();
flyweight3.sayHello();
}
}
可以看到,后续一样的 "sayHello" 对象没有重复创建,而是复用了之前创建的。
2.1. 内部状态与外部状态
享元类设计的关键步骤就是把内部状态和外部状态进行剥离。
- 内部状态可以做为成员变量。
- 外部状态不可以,需要外部传入。
3. 应用
应用在这些场景:
- 节约内存,存在大量相同或者相似的对象。
- 对象需要重复使用。
3.1. DruidDataSource
DruidDataSource 属于数据库连接池,用来缓存数据库连接。
数据库连接可复用,无需大量创建。
缓存池使用了数组实现,连接和连接相关的配置信息被封装在 DruidConnectionHolder 中了。
private volatile DruidConnectionHolder[] connections;
具体的获取逻辑在 getConnectionInternal 中实现。先从缓存读取,没有的话阻塞等待新创建的连接然后返回。
3.2. JDK:Integer#valueOf(int)
Java 使用 Integer 包装类的时候,使用工厂方法 valueOf 的地方有应用了享元模式。
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
可以内部使用了 IntegerCache 来进行缓存。
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 =
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() {}
}
可以看到,在 [-128,127] 的包装类已经在类加载过程中,提前放入了缓存中。
这个区间的包装类使用频繁,这里可以进行复用。
3.3. Glide:Bitmap 缓存
4. 特点
4.1. 优点
- 节约内存,相同或者相似的对象只留一份。
- 外部状态独立,独立的外部状态,在不同的环境中均可以使用。
4.2. 缺点
- 设计复杂,享元需要分离出外部状态和内部状态。
- 外部状态获取耗时,获取外部状态时间较长。
网友评论