享元模式
1.定義:
使用共享對象可有效的支持大量的細(xì)粒度的對象。
2.使用場景:
- 系統(tǒng)中存在大量的相似對象;
- 需要緩沖池的場景;
- 細(xì)粒度的對象都具備較接近的外部狀態(tài),而且內(nèi)部狀態(tài)與環(huán)境無關(guān),也就是講對象沒有特定身份。
3.UML圖

4.詳解:
享元模式是對象池的一種體現(xiàn),是一種結(jié)構(gòu)型設(shè)計模式。它用來盡可能減少內(nèi)存使用量,它適合用于可能存在大量重復(fù)對象的場景,來緩存可共享的對象,達(dá)到對象共享、避免創(chuàng)建過多對象的效果,即提升性能、避免內(nèi)存溢出等。
享元對象中的部分狀態(tài)可以共享,可共享的狀態(tài)稱為內(nèi)部狀態(tài),內(nèi)部狀態(tài)不會隨著環(huán)境變化;不可共享的狀態(tài)稱為外部狀態(tài),它們會隨著環(huán)境的改變而改變。在享元模式中,建立一個對象容器,在經(jīng)典的享元模式中該容器為Map,它的key是內(nèi)部狀態(tài),value是享元對象本身??蛻舳顺绦蛲ㄟ^這個內(nèi)部狀態(tài)從享元工廠中獲取享元對象,如果有緩存則使用緩存對象,否則創(chuàng)建一個享元對象并放入容器中,這樣一來,就避免了創(chuàng)建過多對象的問題。
下面就以買票的案例舉例:詳見代碼
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和火車票具體實現(xiàn)類。接著看車票工廠是如何出票的,代碼中列舉了兩種出票方案,顯然,第一種是有問題的,第二種使用了享元模式緩存對象,有效避免了重復(fù)對象的創(chuàng)建與銷毀:
public static class TicketFactory {
//這種做法是極其危險的,如果短時間內(nèi)有10000人要購from-to的票,會造成大量重復(fù)對象的創(chuàng)建,GC對這些對象的回收會很耗資源。
//如果用戶量更大,意味著請求量更大,很可能導(dǎo)致系統(tǒng)變得極其緩慢,甚至可能導(dǎo)致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("創(chuàng)建對象 ===>" + 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("軟臥");
/**
創(chuàng)建對象 ===>杭州-南京
購買從杭州到南京的坐票火車票,價格12元
使用緩存 ===>杭州-南京
購買從杭州到南京的站票火車票,價格297元
使用緩存 ===>杭州-南京
購買從杭州到南京的軟臥火車票,價格30元
*/
}
從輸出結(jié)果看出,只有第一次查詢杭州到南京的車票是new出來的,后面的查詢都是使用的緩存對象,避免了重復(fù)對象的創(chuàng)建與回收。