刚开始看到这个模式的名字的时候,还感觉看不懂的样子,以为很难,但是看了它的介绍后就没这么恐惧了。享就是共享,元就是一个单元,也就是对象的意思,所以享元就是共享对象,从这点来看享元模式跟单例模式挺像的,或者说享元模式是单例模式的一种扩展。享元模式是对象池的一种实现,它的目的也是达到对象共享,避免创建多对象,以此来提升性能。它并不是只有一个对象,享元对象中的部分状态可以共享,这部分状态称为内部状态,内部状态不会随着环境变化而变化;不会共享的状态称为外部状态,外部状态随着环境变化而变化。享元模式中有一个容器,一般为 Map,它的键就是享元对象的状态,值就享元对象本身,客户端通过这个内部状态来获取享元对象,如果有缓存对象则直接获取,如果没有则创建享元对象并存储到容器中。
第二十二章 享元模式
1.定义
使用共享对象可以有效地支持大量细粒度的对象。( Java 中细粒度的定义:细粒度模型,通俗的讲就是将业务模型中的对象加以细分,从而得到更科学合理的对象模型,直观的说就是划分出很多对象)。
2.使用场景
1).系统中存在大量的相似对象。
2).细粒度的对象都具备接近的外部状态,而且内部状态与环境无关,也就是说对象没有特定身份。
3).需要缓冲池的场景。
3.简单实现
我自己倒没想出什么好的别的例子,就借用书中的例子。买火车票是件很困难的事情,特别是春运期间,现在抢票软件很多,无数人用抢票软件向服务端发出请求,对于每一次请求服务端都得作出回应,但是如果每次查询的车票结果都新建一个对象的话,势必会造成大量对象的创建,而这些对象其实是有些相似之处的,所以我们并不需要创建重复的对象,一般人买火车票,出发地和目的地是不会变的,我们先假设车次只有一趟,所以只有席别不同,然后导致价格不同。这样我们就可以借用享元模式来解决这种场景的问题。
抽象车票接口:
public interface Ticket {
void queryTicket(String bunk);
}
火车票类:
public class TrainTicket implements Ticket {
private String from;
private String to;
private String bunk;
private int price;
public TrainTicket(String from, String to) {
this.from = from;
this.to = to;
}
@Override
public void queryTicket(String bunk) {
if (bunk.equals("无座")) {
price = 229;
} else if (bunk.equals("硬座")) {
price = 229;
} else if (bunk.equals("硬卧")) {
price = 328;
} else if (bunk.equals("软卧")) {
price = 613;
}
System.out.println("查询从 " + from + " 到 " + to + " 的车票,席别为 " + bunk + " 票价为:" + price + " 元");
}
}
public class TrainTicketFactory {
//创建一个容器来保存对象,避免重复创建
private static Map<String, Ticket> sTicketMap = new HashMap<String, Ticket>();
public static Ticket getTicket(String from, String to) {
//将出发地和目的地连起来作为键
String key = from.concat("-").concat(to);
if (sTicketMap.containsKey(key)) {
System.out.println("已经有该对象:" + key);
return sTicketMap.get(key);
} else {
Ticket mTicket = new TrainTicket(from, to);
sTicketMap.put(key, mTicket);
System.out.println("没有该对象:" + key + ",创建一个新对象");
return mTicket;
}
}
}
Ticket ticket1 = TrainTicketFactory.getTicket("北京", "重庆");
ticket1.queryTicket("无座");
System.out.println(ticket1.toString()+","+ticket1.hashCode());
Ticket ticket2 = TrainTicketFactory.getTicket("北京", "重庆");
ticket2.queryTicket("硬座");
System.out.println(ticket2.toString()+","+ticket1.hashCode());
Ticket ticket3 = TrainTicketFactory.getTicket("北京", "重庆");
ticket3.queryTicket("硬卧");
System.out.println(ticket3.toString()+","+ticket1.hashCode());
Ticket ticket4 = TrainTicketFactory.getTicket("北京", "重庆");
ticket4.queryTicket("软卧");
System.out.println(ticket4.toString()+","+ticket1.hashCode());
运行程序,打印结果如下:
可以看到内存地址和 hashCode 都是一样的,所以这个火车票对象只创建了一次,后面都是使用容器中缓存的对象。这样一来,即使我们查询成百上千次,那么还是只会有一个对象,就避免了大量的内存占用和 GC 频繁的对象回收。
4.总结
享元模式的实现比较简单,但是它的作用是极为重要的。
优点:
1).减少应用程序创建的对象,降低程序内存的占用,增强程序性能。
缺点:
1).让程序更复杂,为了让对象实现共享,需要将一些状态定义为外部状态,使逻辑变得复杂。
2).将享元对象的状态外部化,但是读取外部状态会使运行时间稍长。