享元模式
1.定义:
使用共享对象可有效的支持大量的细粒度的对象。
2.使用场景:
- 系统中存在大量的相似对象;
- 需要缓冲池的场景;
- 细粒度的对象都具备较接近的外部状态,而且内部状态与环境无关,也就是讲对象没有特定身份。
3.UML图
4.详解:
享元模式是对象池的一种体现,是一种结构型设计模式。它用来尽可能减少内存使用量,它适合用于可能存在大量重复对象的场景,来缓存可共享的对象,达到对象共享、避免创建过多对象的效果,即提升性能、避免内存溢出等。
享元对象中的部分状态可以共享,可共享的状态称为内部状态,内部状态不会随着环境变化;不可共享的状态称为外部状态,它们会随着环境的改变而改变。在享元模式中,建立一个对象容器,在经典的享元模式中该容器为Map,它的key是内部状态,value是享元对象本身。客户端程序通过这个内部状态从享元工厂中获取享元对象,如果有缓存则使用缓存对象,否则创建一个享元对象并放入容器中,这样一来,就避免了创建过多对象的问题。
下面就以买票的案例举例:详见代码
public interface Ticket {
void showTicketInfo(String bunk);
}
public static class TrainTicket implements Ticket {
String from, to, bunk;
int price;
public TrainTicket(String from, String to) {
this.from = from;
this.to = to;
}
@Override
public void showTicketInfo(String bunk) {
price = new Random().nextInt(300);
System.out.println("购买从" + from + "到" + to + "的" + bunk + "火车票,价格" + price + "元");
}
}
上面定义了一个车票接口Ticket和火车票具体实现类。接着看车票工厂是如何出票的,代码中列举了两种出票方案,显然,第一种是有问题的,第二种使用了享元模式缓存对象,有效避免了重复对象的创建与销毁:
public static class TicketFactory {
//这种做法是极其危险的,如果短时间内有10000人要购from-to的票,会造成大量重复对象的创建,GC对这些对象的回收会很耗资源。
//如果用户量更大,意味着请求量更大,很可能导致系统变得极其缓慢,甚至可能导致OOM
public Ticket getTicketInfo(String from, String to) {
return new TrainTicket(from, to);
}
//使用享元模式修改一下,加入缓存容器
private static Map<String, Ticket> stringTicket = new ConcurrentHashMap<>();
public static Ticket getTicket(String from, String to) {
Ticket ticket;
String key = from + "-" + to;
if (stringTicket.containsKey(key)) {
System.out.println("使用缓存 ===>" + key);
ticket = stringTicket.get(key);
} else {
System.out.println("创建对象 ===>" + key);
ticket = new TrainTicket(from, to);
stringTicket.put(key, ticket);
}
return ticket;
}
}
测试代码:
public static void main(String[] args) {
Ticket ticket0 = TicketFactory.getTicket("杭州", "南京");
ticket0.showTicketInfo("坐票");
Ticket ticket1 = TicketFactory.getTicket("杭州", "南京");
ticket1.showTicketInfo("站票");
Ticket ticket2 = TicketFactory.getTicket("杭州", "南京");
ticket2.showTicketInfo("软卧");
/**
创建对象 ===>杭州-南京
购买从杭州到南京的坐票火车票,价格12元
使用缓存 ===>杭州-南京
购买从杭州到南京的站票火车票,价格297元
使用缓存 ===>杭州-南京
购买从杭州到南京的软卧火车票,价格30元
*/
}
从输出结果看出,只有第一次查询杭州到南京的车票是new出来的,后面的查询都是使用的缓存对象,避免了重复对象的创建与回收。