【设计模式】享元模式

定义

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

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

应用场景

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

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

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

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

例子

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

  1. 创建ITicket接口,提供一个showInfo方法,传入席次信息
1
2
3
public interface ITicket {
void showInfo(String trunk);
}
  1. 创建具体的TIcket类,继承ITicket接口,创建出发站,终点站和价格三个属性,重写showInfo方法,返回票的信息。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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的抽象。
1
2
3
4
5
6
public class TicketFactory {

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

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

  1. 改造TicketFactory工厂类,增加缓存机制,创建一个map容器保存对象,不存在才会去创建对象,存在就直接返回该对象。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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. 重写测试方法。
1
2
3
4
5
6
7
8
9
10
11
12

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是怎么将数据存在缓存中,然后从缓存中去取的。

1
2
3
4
5
6
7
8
9
10
11
12
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.使系统,程序的逻辑复杂化。



The End.