【设计模式】享元模式

Alan 250 2019-06-04
定义

享元模式又称为轻量级模式,是对象池的一种实现,类似于线程池。线程池可以避免重复的创建和销毁对象,消耗性能。提供了减少对象数量从而改善应用所需的对象结构的方式。其宗旨是共享细粒度对象,将对象的同意访问集中起来,不必为每一个访问者单独创建一个对象,以此来降低系统的消耗,属于结构型的模式。

享元模式把对象的状态分为内部状态和外部状态,内部状态是不变的,外部状态是变化的;然后通过不变的部分,达到减少对象数量并减少内存的目的。

应用场景

当系统中的多处需要一组信息,可以把这些信息封装到一个对象中,然后对对象进行缓存,这样,一个对象就可以提供给对多处访问。避免了多次使用同时创建对象,造成内存的消耗,影响性能。

享元模式其实就是工厂模式的一个改进机制,享元模式同样要求创建一个或一组对象,就是通过工厂方法生成对象的,只不过享元模式为工厂创建了一个缓存的功能。主要总结为一下的应用场景。

  1. 常常应用于系统底层的开发,以便解决系统的性能。
  2. 系统有大量的相似对象,需要缓冲池的场景。

在生活中的享元模式也很常见,比如各种中介和全国联网的医保。

例子

生活中肯定会遇到要抢火车篇的情况。那些刷票软件实际上是将我们的信息保存下来,然后定时地去余票里面查询有没有自己需要的余票。假如一张火车票包含出发站,终点站,席次 的信息。然后这里写一个程序利用这些信息去查询有没有余票。

  1. 创建ITicket接口,提供一个showInfo方法,传入席次信息
public interface ITicket {
    void showInfo(String trunk);
}
  1. 创建具体的TIcket类,继承ITicket接口,创建出发站,终点站和价格三个属性,重写showInfo方法,返回票的信息。
import java.util.Random;

public class Ticket implements ITicket {
    private String from;
    private String to;
    private int price;

    public Ticket(String from, String to) {
        this.from = from;
        this.to = to;
    }

    @Override
    public void showInfo(String trunk) {
        this.price = new Random().nextInt(500);
        System.out.println(String.format("%s->%s:%s 价格:%s 元", this.from, this.to, trunk, this.price));
    }
}
  1. 创建一个ticket工厂,提供静态的查询余票的方法,返回ITicket的抽象。
public class TicketFactory {

    public static ITicket queryTicket(String from, String to) {
        return new Ticket(from, to);
    }
}
  1. 测试代码。
public class Test {
    public static void main(String[] args) {
        ITicket iTicket = TicketFactory.queryTicket("杭州东", "汉口");
        iTicket.showInfo("硬座");
    }
}
  1. 查看结果。

但是这样的写法其实也是每次去查询都创建了一个新的对象,那么其实可以改造一下。

  1. 改造TicketFactory工厂类,增加缓存机制,创建一个map容器保存对象,不存在才会去创建对象,存在就直接返回该对象。
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class TicketFactory {

    private static Map<String, ITicket> sticketPool = new ConcurrentHashMap<>();

    public static ITicket queryTicket(String from, String to) {
        String key = from + "->" + to;
        if (sticketPool.containsKey(key)) {
            System.out.println("使用缓存: " + key);
            return TicketFactory.sticketPool.get(key);
        }
        System.out.println("首次查询,创建对象:" + key);
        ITicket iTicket = new Ticket(from, to);
        TicketFactory.sticketPool.put(key,iTicket);
        return iTicket;
    }
}
  1. 重写测试方法。

public class Test {

    public static void main(String[] args) {
        ITicket iTicket = TicketFactory.queryTicket("孝感东","汉口");
        iTicket.showInfo("上铺");
        iTicket = TicketFactory.queryTicket("孝感东","汉口");
        iTicket.showInfo("上铺");
        iTicket = TicketFactory.queryTicket("杭州东","汉口");
        iTicket.showInfo("中铺");
    }
}
  1. 查看新的测试结果。

享元模式在源码中的应用

来看看在Integer中,Integer是怎么将数据存在缓存中,然后从缓存中去取的。

public class Test1 {
    public static void main(String[] args) {
        Integer a = Integer.valueOf(100);
        Integer b = 100;

        Integer c = Integer.valueOf(1000);
        Integer d = 1000;

        System.out.println("a==b:" + (a == b));
        System.out.println("c==d:" + (c == d));
    }
}

运行上面的代码,可以看到,a和b是相等的,c和d是不相等的。

这是为什么呢,这里点开Integer的valueOf方法去看看。

可以看到,如果传入的i值大于等于IntegerCache.low或者小于等于IntegerCache.high,那么就会从cha么就会从cache缓存里面取,如果不是的话就新建一个Integer对象。而IntegerCache.low的值是-128,IntegerCache.high的值是127。

总结

享元模式的优点:

​ 1.减少对象的创建,降低内存中对象的数量,降低系统中的内存,提高效率。

​ 2.减少内存之外的其他资源的占用。

享元模式的缺点:

​ 1.关注内部,外部状态,关注线程安全问题。

​ 2.使系统,程序的逻辑复杂化。


# 设计模式