定義
? 享元模式(Flyweight)是對(duì)象池的一種實(shí)現(xiàn)编矾。享元模式用來(lái)盡可能的減少內(nèi)存使用量瓤逼,適用于可能存在大量重復(fù)對(duì)象的場(chǎng)景酥泞,來(lái)緩存可共享的對(duì)象達(dá)到對(duì)象共享给猾,避免創(chuàng)建過(guò)多對(duì)象的效果。
? 享元對(duì)象中,可以共享的狀態(tài)是內(nèi)部狀態(tài)缰冤,內(nèi)部狀態(tài)不會(huì)隨著環(huán)境的變化犬缨,不可以共享的狀態(tài)稱為外部狀態(tài),外部狀態(tài)會(huì)隨著環(huán)境變化而變化棉浸。享元模式中會(huì)創(chuàng)建一個(gè)對(duì)象容器怀薛,經(jīng)典的享元模式中這個(gè)容器是一個(gè)Map,它的key是享元對(duì)象的內(nèi)部狀態(tài)value為享元對(duì)象本身迷郑。使用者通過(guò)內(nèi)部狀態(tài)從享元工廠中獲取享元對(duì)象枝恋,如果有緩存則使用緩存,如果沒(méi)有則創(chuàng)建一個(gè)對(duì)象存入容器中嗡害。
UML圖
享元模式焚碌,主要有享元工廠和享元對(duì)象角色
-
Flyweight
享元對(duì)象的抽象類或者接口
-
ConcreteFlyweight
具體的享元對(duì)象
-
FlyweightFactory
享元工廠,負(fù)責(zé)創(chuàng)建享元對(duì)象和管理對(duì)象池
示例
以購(gòu)買火車票為例霸妹,當(dāng)我們?cè)诰W(wǎng)站查詢火車票時(shí)十电,一般會(huì)輸入起始站和終點(diǎn)站查詢,這兩個(gè)值對(duì)于一個(gè)車票對(duì)象來(lái)說(shuō)是不會(huì)變的叹螟,為內(nèi)部狀態(tài)鹃骂,當(dāng)然其價(jià)格會(huì)根據(jù)所選鋪位的不同而不同,為外部狀態(tài)首妖。
/**
* 車票類的抽象
*/
public interface Ticket {
//打印車票信息的方法偎漫,傳入車票的鋪位
void printTicketInfo(String bunk);
}
/**
* 享元對(duì)象的具體實(shí)現(xiàn)
* 其內(nèi)部狀態(tài)是起始站from和終點(diǎn)站key
* 外部狀態(tài)為票價(jià)price,其價(jià)格根據(jù)鋪位的不同而不同
*/
public class TrainTicket implements Ticket {
private String from;
private String to;
private int price;
public TrainTicket(String from, String to) {
this.from = from;
this.to = to;
}
@Override
public void printTicketInfo(String bunk) {
price = new Random().nextInt(500);
System.out.println("從" + from + "到" + to + "的" + bunk + "票價(jià)為 :" + price + "元");
}
}
/**
* 享元工廠
* 使用ConcurrentHashMap緩存享元對(duì)象
* 使用from + "-" + to作為key存儲(chǔ)享元對(duì)象
*/
public class TicketFactory {
private static ConcurrentHashMap<String,Ticket> sTicketMap = new ConcurrentHashMap<>();
public static Ticket getTicket(String from,String to){
String key = from + "-" + to;
if (sTicketMap.containsKey(key)){
System.out.println("從緩存取" + key);
return sTicketMap.get(key);
}else {
System.out.println("實(shí)例化" + key);
Ticket ticket = new TrainTicket(from, to);
sTicketMap.put(key,ticket);
return ticket;
}
}
public static void main(String args[]) {
/**
* 客戶端查詢調(diào)用
*/
Ticket ticket = TicketFactory.getTicket("深圳", "北京");
ticket.printTicketInfo("硬臥");
Ticket ticket1 = TicketFactory.getTicket("深圳", "北京");
ticket1.printTicketInfo("硬座");
Ticket ticket2 = TicketFactory.getTicket("深圳", "北京");
ticket2.printTicketInfo("軟臥");
}
實(shí)例化深圳-北京
從深圳到北京的硬臥票價(jià)為 :154元
從緩存取深圳-北京
從深圳到北京的硬座票價(jià)為 :427元
從緩存取深圳-北京
從深圳到北京的軟臥票價(jià)為 :264元
我們看到示例中只有第一次從工廠中查詢時(shí)新建了對(duì)象有缆,而后面兩次都是從緩存中獲取的象踊。
Android源碼中的享元模式
Android應(yīng)用是事件驅(qū)動(dòng)的,每個(gè)事件都會(huì)轉(zhuǎn)化為一個(gè)系統(tǒng)消息棚壁,即Message杯矩。那么應(yīng)用中很可能就會(huì)產(chǎn)生很多的Message對(duì)象,而Message對(duì)象的獲取就是使用了享元模式袖外。在獲取Message對(duì)象時(shí)史隆,我們一般調(diào)用Obtain方法
/**
* Return a new Message instance from the global pool. Allows us to
* avoid allocating new objects in many cases.
*/
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
這里看到有m.next,推測(cè)使用了鏈表結(jié)構(gòu)曼验。繼續(xù)看一下obtain方法涉及到的這幾個(gè)屬性的定義
/ sometimes we store linked lists of these things
/*package*/ Message next;
/** @hide */
public static final Object sPoolSync = new Object();
private static Message sPool;
? sPoolSync為Object類型的同步鎖泌射,而sPool就是一個(gè)Message類型的對(duì)象,每個(gè)Message對(duì)象都有一個(gè)同類型的next字段鬓照,這個(gè)next字段指向下一個(gè)可用的Message熔酷,最后一個(gè)可用的Message的next字段為null。這樣所有可用的Message對(duì)象就通過(guò)next串聯(lián)成了一個(gè)可用的Message池豺裆。
? 這里首先判斷鏈表的表頭sPool為空的話就只new一個(gè)Message對(duì)象返回拒秘。當(dāng)sPool不為空時(shí),將sPool賦值對(duì)象賦值給m,sPool = m.next 將sPool對(duì)象對(duì)象賦值為m的next屬性躺酒,并將m的next節(jié)點(diǎn)置為null押蚤,最后將m對(duì)象返回,這樣就取出了鏈表的表頭羹应。而鏈表中的對(duì)象是什么時(shí)候存進(jìn)去的呢揽碘,我們看一下Message對(duì)象的回收方法
/**
* Recycles a Message that may be in-use.
* Used internally by the MessageQueue and Looper when disposing of queued Messages.
*/
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = -1;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
這里將當(dāng)前對(duì)象的next節(jié)點(diǎn)指向sPool,并將sPool指向當(dāng)前Message對(duì)象量愧。即將當(dāng)前回收的Message對(duì)象插入的鏈表的表頭钾菊。
? Message通過(guò)在內(nèi)部構(gòu)建一個(gè)鏈表來(lái)維護(hù)一個(gè)被回收的Message對(duì)象的對(duì)象池子帅矗,當(dāng)用戶調(diào)用obtain方法時(shí)偎肃,會(huì)首先從池中取,如果池中沒(méi)有可以復(fù)用的對(duì)象則創(chuàng)建這個(gè)新的Message對(duì)象浑此,這個(gè)Message對(duì)象在使用完后會(huì)回收到這個(gè)對(duì)象池中累颂,以便下一次調(diào)用obtain方法時(shí)可以復(fù)用。