《Android源碼設計模式解析與實戰》讀書筆記(二十二)
第二十二章、享元模式
享元模式是結構型設計模式之一,是對對象池的一種實現。就像它的名字一樣,共用對象,避免重複的建立。我們常用的String 就是使用了共用模式,所以String類型的對象建立後就不可改變,如果當兩個String對象所包含的內容相同時,JVM只建立一個String對象對應這兩個不同的對象引用。
1.定義
採用一個共用來避免大量擁有相同內容對象的開銷。使用享元模式可有效支援大量的細粒度對象。
2.使用情境
(1)系統中存在大量的相似對象。
(2)細粒度的對象都具備較接近的外部狀態,而且內部狀態與環境不關,也就是說對象沒有特定身份。
(3)需要緩衝池的情境。
PS:內部狀態與外部狀態:在享元對象內部並且不會隨著環境改變而改變的共用部分,可以稱之為享元對象的內部狀態,反之隨著環境改變而改變的,不可共用的狀態稱之為外部狀態。
3.UML類圖
享元模式分為單純享元模式和複合享元模式,是複合享元模式。
(1)Flyweight:享元對象抽象基類或者介面。
(2)ConcreateFlyweight:具體的享元對象,如果有內部狀態的話,必須負責為內部狀態提供儲存空間。
(3)UnsharadConcreateFlyweight:複合享元角色所代表的對象是不可以共用的,並且可以分解成為多個單純享元對象的組合。單純享元模式沒有此項,這也是兩者在結構上的區別。
(4)FlyweightFactoiy:享元工廠,負責管理享元對象池和建立享元對象。
(5)Client:維護對所有享元對象的引用,而且還需要儲存對應的外蘊狀態。
4.簡單實現
情景:過年買火車票的時候,我們需要查詢車票的情況,那麼如果每次查詢車票時都建立一個結果,那麼必然會大量的建立出許多重複的對象,頻繁的去銷毀他們,使得GC任務繁重。那麼這時我們可以使用享元模式,將這些對象緩衝起來,查詢時優先使用緩衝,沒有緩衝在重新建立。
首先是Ticket介面(Flyweight):
public interface Ticket { public void showTicketInfo(String bunk);}
TrainTicket具體實作類別(ConcreateFlyweight):
//火車票public class TrainTicket implements Ticket{ public String from; // 始發地 public String to; // 目的地 public String bunk; //鋪位 public 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); }}
TicketFactory 管理查詢火車票(FlyweightFactoiy):
public class TicketFactory { static Map 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("建立對象 ==> " + key); Ticket ticket = new TrainTicket(from, to); sTicketMap.put(key, ticket); return ticket; } }}
查詢:
final class Client { public static void main(String[] args) { Ticket ticket01 = TicketFactory.getTicket("北京", "青島"); ticket01.showTicketInfo("上鋪"); Ticket ticket02 = TicketFactory.getTicket("北京", "青島"); ticket02.showTicketInfo("下鋪"); Ticket ticket03 = TicketFactory.getTicket("北京", "西安"); ticket03.showTicketInfo("坐票"); }}
結果
建立對象 ==> 北京-青島購買 從 北京 到 青島的上鋪火車票, 價格:71使用緩衝 ==> 北京-青島購買 從 北京 到 青島的下鋪火車票, 價格:32建立對象 ==> 北京-西安購買 從 北京 到 西安的坐票火車票, 價格:246
5.Android源碼中的實現1.Message
因為Android是事件驅動的,因此如果通過new建立 Message 就會建立大量的 Message 對象,導致記憶體佔用率高,頻繁GC等問題。那麼 Message 就採用了享元模式。
Message通過next成員變數保有對下一個Message的引用,最後一個可用Message的next則為空白。從而構成了一個Message鏈表。Message Pool就通過該鏈表的表頭管理著所有閑置的Message,一個Message在使用完後可以通過recycle()方法進入Message Pool,並在需要時通過obtain靜態方法從Message Pool擷取。Message 承擔了享元模式中3個元素的職責,即是Flyweight抽象,又是ConcreateFlyweight角色,同時又承擔了FlyweightFactoiy管理對象池的職責。
所以使用Message推薦obtain(),不要去new了。
//1。使用new Message() //Message mess = new Message(); //2。使用Message.obtain() Message mess = Message.obtain(); mess.what = 1; //Message mess = mHandler.obtainMessage(1); 與上兩行的代碼一樣,可以參考源碼查看 mHandler.sendMessage(mess);
6.總結1.優點
(1)大大減少應用程式建立的對象,降低程式記憶體的佔用,增強程式的效能。
(2)使用享元模式,可以讓享元對象可以在不同的環境中被共用。
2.缺點
(1)使得系統更加複雜。為了使對象可以共用,需要將一些狀態外部化,這使得程式的邏輯複雜化。
(2)享元模式將需、享元對象的狀態外部化,而讀取外部狀態使得已耗用時間稍微變長。