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

1、享元模式的概念

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

2、何时使用享元模式

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

享元模式是一个考虑系统性能的设计模式,经过使用享元模式能够节约内存空间,提升系统的性能。数据库

3、如何使用享元模式

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();
    }

}

输出:bash

第一次,会从数据库中检索。
【点火故障,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。this

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

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

4、总结

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

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

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

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

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

相关文章
相关标签/搜索
本站公众号
   欢迎关注本站公众号,获取更多信息