《一天一模式》— 享元模式

烂漫一生 提交于 2020-05-05 04:12:51

一、享元模式的概念

运用共享技术有效地支持大量细粒度对象的复用。系统只使用少量的对象,而这些对象都很相似,状态变化很小,可以实现对象的多次复用。由于享元模式要求能够共享的对象必须是细粒度对象,因此它又称为轻量级模式,它是一种对象结构型模式。

二、什么时候使用享元模式

我个人理解为,把基本不变、并可以多次使用的细颗粒度(小)对象,加载到内存中保存起来,然后对外提供业务。业务是指组合这些细颗粒度对象提供更丰富的数据,每次使用时不必再加载一次,直接从内存中读取。

享元模式是一个考虑系统性能的设计模式,通过使用享元模式可以节约内存空间,提高系统的性能。

三、如何使用享元模式

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

代码总共分为五个类:

  1. DTCDataBase:模拟数据库的,没什么重点,假装是从磁盘读取,性能不高;
  2. DTCInfo:享元对象,是概念中细颗粒度(小)的对象,保存一个故障信息,会被缓存;
  3. DTCInfoFactory:享元工厂,属于核心类,也没有特别难理解的,在工厂中维护一个内存池,保存从数据库加载的享元对象;
  4. FAQ:用于实现需求,模拟不同的故障需要给车主返回相关的故障列表;
  5. Client:启动类,同样的业务做两边,看看是否第一次从数据库检索,第二次从内存检索;

3.2 享元模式的好处

节省空间: 如果享元模式用于缓存比较大的对象,只new一个后即可,不会有频繁new导致空间不足的问题发生;

性能提高: 只有第一次使用对象,会从资源中加载,后续的对象使用直接从享元模式提供的工厂中获取即可;

3.3 思考与单例模式的不同

我的理解是,单例模式保存了一个对象在内存中只有一份

享元模式是确保一堆对象在其池中只有一份,并且根据业务需求,对外提供不同的对象列表。这里要提到两个概念:Intrinsic与Extrinsic。

Intrinsic的意思是固有的、本质的,意思是什么情况下都不会改变的对象,上面的代码示例中的DTCInfo就是Intrinsic,所以它适合被缓存。

Extrinsic的意思是外在的、非本质的,意思是会发生变化的对象,比如engineOn返回A,C故障,engineOff返回B,D,E故障,其他情况返回其他的故障组合,这个故障组合就是Extrinsic,它不能被缓存,因为变化很频繁。

四、总结

享元对象虽然结构上复杂了一点,有单例、简单工厂等等模式内嵌其中,但是从功能上来讲很简单,把不会改变的东西缓存起来,并根据不同业务做不同的返回,起到了节省空间提高性能的功效。

这个模式跟备忘录模式的现状有些相同。

  • 首先,要确保在内存中的对象不会被垃圾回收;
  • 其次,在分布式部署的系统架构下,使用JVM内存则行不通;

现有的缓存中间件,已经逐渐可以胜任这个模式了(例如Redis),中间件也提供了很多查询方式,所以这个模式渐渐用的不算多了。

以上就是我对享元模式的一些理解,有不足之处请大家指出,谢谢。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!