基本概念
享元模式(Flyweight Pattern) 也称蝇量模式,运用共享技术有效地支持大量细粒度的对象。常见的实现有数据库连接池,缓冲池,常量池等池技术。可以有效减少创建对象的性能开销,减少内存占用提高物业性能。
网上有一个列子说的很形象
网络联机下棋的时候,一台服务器连接了多个客户端(玩家),如果我们每个棋子都要创建对象,那一盘棋可能就有上百个对象产生,玩家多点的话,因为内存空间有限,一台服务器就难以支持了,所以这里要使用享元模式。
享元模式有两种状态内部状态和外部状态,内部状态就是共享出来的信息,外部状态是会根据环境的变化能变化不可共享。就类似棋盘每个用户看到的棋盘都是一样的就属于内部状态,又如棋子只有两种颜色黑白,颜色就可以定义为一个内部状态,而每一个棋子的坐标是根据棋手玩法不同而动态变化的就是一个外部状态。
UML
角色分析
- 抽象享元角色 Flyweight 定义出对象的外部状态和内部状态的接口
- 具体的享元实现 ConcreteFlyweight 实现抽象角色的具体接口
- 不可共享角色 UnsharedConcreteFlyweight 不能共享的数据
- 享元工厂类 FlyweightFactory 构建一个容器池如集合Map 直接从池中获取对象
代码实现
有这样一个需求,假设客户需要做一个门户网站,门户网站包括了新闻,游戏,体育等等不同的模块,每个模块又略有不同,如果在并发不高的情况下,每个模板单独申请内存来处理,相当于一个网站有多份实例,造成资源浪费,我们采用享元模式来完成设计。
// 抽象享元角色 Flyweight
public abstract class Website {
// user 是不同的外部状态
public abstract void use(User user);
}
// 不可共享角色 UnsharedConcreteFlyweight
@AllArgsConstructor
@Getter
public class User {
private String name;
}
//具体的享元实现 ConcreteFlyweight
public class ConcreteWebsite extends Website {
private String type;
public ConcreteWebsite(String type) {
this.type = type;
}
@Override
public void use(User user) {
System.out.println("网站分类:" + type + "用户名:" + user.getName());
}
}
// 享元工厂类 FlyweightFactory
public class WebsiteFactory {
// 用一个Map来保存已经创建好的Website 如果type不变一直返回的同一个website
private Map<String, Website> pool = new HashMap<>();
public Website getWebsite(String type) {
if (pool.containsKey(type)) {
return pool.get(type);
} else {
ConcreteWebsite concreteWebsite = new ConcreteWebsite(type);
pool.put(type, concreteWebsite);
return concreteWebsite;
}
}
public int getWebsiteCount(){
return pool.size();
}
}
public class FlyweightTest {
public static void main(String[] args) {
WebsiteFactory websiteFactory = new WebsiteFactory();
Website newWebSite = websiteFactory.getWebsite("新闻");
newWebSite.use(new User("z3"));
newWebSite.use(new User("w5"));
Website newWebSite2 = websiteFactory.getWebsite("新闻");
newWebSite2.use(new User("l4"));
Website peWebsite = websiteFactory.getWebsite("体育");
peWebsite.use(new User("z3"));
peWebsite.use(new User("w6"));
System.out.println(websiteFactory.getWebsiteCount());
}
}
//网站分类:新闻用户名:z3
//网站分类:新闻用户名:w5
//网站分类:新闻用户名:l4
//网站分类:体育用户名:z3
//网站分类:体育用户名:w6
//2 最终的网站池里面只有2个实例,不同外部状态用户有自己的属性
JDK源码
在JDK中Integer中的valueOf方法先判断在一个静态内部类的IntegerCache先去找是否在缓存中如果存在直接返回,不存在重新new一个
// 缓冲初始化 128-127
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() {}
}
public static Integer valueOf(int i) {
// 如果在-128 到 127 直接直接用IntegerCache中已经存在值返回
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
// 不在缓冲中new一个
return new Integer(i);
}
使用细节
- 在系统有大量使用对象,并且对象的大部分状态可以外部化就可以考虑使用享元模式
- 用一个标识来保存数据,一般用Map数据结构保存在一个Factory中
- 享元模式大大减少了对象创建,开销提高了性能
- 享元模式使用注意需要区分出来哪些是外部状态哪些是内部状态
- 享元模式常用的场景有数据库连接池,缓存池等一些池技术,核心思想就是把一些常用的对象保存到一个地方,下次使用直接取不需要重新创建,也不会创建多份