一、享元模式的概念
运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式,它是一种对象结构型模式。
二、什么时候使用享元模式
我个人理解为,把基本不变、并可以多次使用的细颗粒度(小)对象,加载到内存中保存起来,然后对外提供业务。业务是指组合这些细颗粒度对象提供更丰富的数据,每次使用时不必再加载一次,直接从内存中读取。
享元模式是一个考虑系统性能的设计模式,通过使用享元模式可以节约内存空间,提高系统的性能。
三、如何使用享元模式
3.1 实现方式
还是以一个需求进行说明:假设汽车有很多故障的解决信息FAQ信息,当发生某种故障时,找出对应的几条FAQ信息,把他们返回给用户。
看一下类图和代码:
// 模拟故障数据库
// 假设是在磁盘系统保存这些数据
public class DTCDataBase {
private static Map<String, String> info = new HashMap<>();
static {
info.put("1", "故障A");
info.put("2", "故障B");
info.put("3", "故障C");
info.put("4", "故障D");
info.put("5", "故障E");
info.put("6", "故障F");
info.put("7", "故障G");
}
private DTCDataBase() {
}
public static String getDTC(String code) {
System.out.println("DTCDataBase: 从数据库中检索信息。");
return info.get(code);
}
}
// 故障信息的享元对象
public class DTCInfo {
private String info;
public DTCInfo(String code) {
// 创建对象时,去数据库中查询故障信息
this.info = DTCDataBase.getDTC(code);
}
public void print() {
System.out.println("故障信息:" + info);
}
}
// 故障信息的享元工厂
public class DTCInfoFactory {
// 创建一个享元对象的内存池
private Map<String, DTCInfo> pool = new HashMap<>();
// 开始单例模式
private static DTCInfoFactory singleton = new DTCInfoFactory();
private DTCInfoFactory() {
}
public static DTCInfoFactory getInstance() {
return singleton;
}
// 结束单例模式
// 获取享元对象
public synchronized DTCInfo getDTCInfo(String code) {
// 先从池中检索,如果有就返回缓存的享元对象
// 如果没有就去数据库中检索,检索后的结果在放入池中
DTCInfo dtc = pool.get(code);
if (dtc == null) {
dtc = new DTCInfo(code);
pool.put(code, dtc);
}
return dtc;
}
}
// FAQ,用于测试
public class FAQ {
// 模拟引擎故障,FAQ返回故障A,C
public void engineOn() {
DTCInfoFactory dtcInfoFactory = DTCInfoFactory.getInstance();
dtcInfoFactory.getDTCInfo("1").print();
dtcInfoFactory.getDTCInfo("3").print();
}
// 模拟熄火故障,FAQ返回故障B,D,E
public void engineOff() {
DTCInfoFactory dtcInfoFactory = DTCInfoFactory.getInstance();
dtcInfoFactory.getDTCInfo("2").print();
dtcInfoFactory.getDTCInfo("4").print();
dtcInfoFactory.getDTCInfo("5").print();
}
}
public class Client {
public static void main(String[] args) {
// 第一次会从数据库中检索;
System.out.println("第一次,会从数据库中检索。");
test();
System.out.println();
System.out.println("第二次就会从享元工厂中检索。");
// 第二次就会从享元工厂中检索;
test();
}
private static void test() {
FAQ faq = new FAQ();
// 点火故障,FAQ会给出的故障信息列表
System.out.println("【点火故障,FAQ会给出的故障信息列表】");
faq.engineOn();
System.out.println("-------------------------");
// 熄火故障,FAQ会给出的故障信息列表
System.out.println("【熄火故障,FAQ会给出的故障信息列表】");
faq.engineOff();
}
}
输出:
第一次,会从数据库中检索。
【点火故障,FAQ会给出的故障信息列表】
DTCDataBase: 从数据库中检索信息。
故障信息:故障A
DTCDataBase: 从数据库中检索信息。
故障信息:故障C
-------------------------
【熄火故障,FAQ会给出的故障信息列表】
DTCDataBase: 从数据库中检索信息。
故障信息:故障B
DTCDataBase: 从数据库中检索信息。
故障信息:故障D
DTCDataBase: 从数据库中检索信息。
故障信息:故障E
第二次就会从享元工厂中检索。
【点火故障,FAQ会给出的故障信息列表】
故障信息:故障A
故障信息:故障C
-------------------------
【熄火故障,FAQ会给出的故障信息列表】
故障信息:故障B
故障信息:故障D
故障信息:故障E
代码总共分为五个类:
- DTCDataBase:模拟数据库的,没什么重点,假装是从磁盘读取,性能不高;
- DTCInfo:享元对象,是概念中细颗粒度(小)的对象,保存一个故障信息,会被缓存;
- DTCInfoFactory:享元工厂,属于核心类,也没有特别难理解的,在工厂中维护一个内存池,保存从数据库加载的享元对象;
- FAQ:用于实现需求,模拟不同的故障需要给车主返回相关的故障列表;
- Client:启动类,同样的业务做两边,看看是否第一次从数据库检索,第二次从内存检索;
3.2 享元模式的好处
节省空间: 如果享元模式用于缓存比较大的对象,只new一个后即可,不会有频繁new导致空间不足的问题发生;
性能提高: 只有第一次使用对象,会从资源中加载,后续的对象使用直接从享元模式提供的工厂中获取即可;
3.3 思考与单例模式的不同
我的理解是,单例模式保存了一个对象在内存中只有一份。
享元模式是确保一堆对象在其池中只有一份,并且根据业务需求,对外提供不同的对象列表。这里要提到两个概念:Intrinsic与Extrinsic。
Intrinsic的意思是固有的、本质的,意思是什么情况下都不会改变的对象,上面的代码示例中的DTCInfo就是Intrinsic,所以它适合被缓存。
Extrinsic的意思是外在的、非本质的,意思是会发生变化的对象,比如engineOn返回A,C故障,engineOff返回B,D,E故障,其他情况返回其他的故障组合,这个故障组合就是Extrinsic,它不能被缓存,因为变化很频繁。
四、总结
享元对象虽然结构上复杂了一点,有单例、简单工厂等等模式内嵌其中,但是从功能上来讲很简单,把不会改变的东西缓存起来,并根据不同业务做不同的返回,起到了节省空间和提高性能的功效。
这个模式跟备忘录模式的现状有些相同。
- 首先,要确保在内存中的对象不会被垃圾回收;
- 其次,在分布式部署的系统架构下,使用JVM内存则行不通;
现有的缓存中间件,已经逐渐可以胜任这个模式了(例如Redis),中间件也提供了很多查询方式,所以这个模式渐渐用的不算多了。
以上就是我对享元模式的一些理解,有不足之处请大家指出,谢谢。
来源:oschina
链接:https://my.oschina.net/u/2450666/blog/4265582