Java設計模式之從[Dota地圖]分析享元(Flyweight)模式
在Dota遊戲的地圖中有幾百棵樹,現在假設這些樹木無非是這兩種:白楊、楓樹,數量一共為400棵,那麼,在裝載這個地圖場景的時候,我們是不是應該給這400課樹一一建立物件呢?(如:MapItem tree1 = new MapItem("白楊");MapItem tree2 = new MapItem("楓樹");MapItem tree3 = new MapItem("白楊");……)哪怕這些語句寫在迴圈中減少了我們程式碼書寫的工作量,對於系統來說,是十分消耗資源的一件事情,因為系統要生成400個物件。可見這樣十分影響效率。那麼,我們能不能用一些優化技術呢?當然可以,那就是使用“享元模式”。如果看到這裡還不明白的話,接著往下看就能夠明白了。
享元模式的意圖是運用共享技術有效地支援大量細粒度的物件。在上面的例子中,我們把所有的同一種類的樹指向同一個物件,這樣可以來節省記憶體的使用,提高程式效率,請看以下程式碼:
interface FlyweightItem { void draw(); } class FlyweightMapItem implements FlyweightItem { String name; public FlyweightMapItem(MapItem map){ this.name = map.name; } public void draw(){ System.out.printf("正在繪製%s\n", name); } } class MapItem { String name; public MapItem(String name){ this.name = name; } } class FlyweightMapFactory { private static FlyweightMapFactory flyweightMapFactory = new FlyweightMapFactory(); private HashMap<String, FlyweightMapItem> itemMap = new HashMap<String, FlyweightMapItem>(); private int itemCount = 0; public int getItemCount(){ return itemCount; } public static FlyweightMapFactory getFlyweightMapFactory(){ return flyweightMapFactory; } public FlyweightMapItem getItem(MapItem item){ if (itemMap.containsKey(item.name)){ return itemMap.get(item.name); } else { FlyweightMapItem map = new FlyweightMapItem(item); itemCount ++; itemMap.put(item.name, map); return map; } } } class Flyweight { public static void main(String[] args) { FlyweightItem item1 = FlyweightMapFactory.getFlyweightMapFactory().getItem(new MapItem("楓樹")); FlyweightItem item2 = FlyweightMapFactory.getFlyweightMapFactory().getItem(new MapItem("楓樹")); FlyweightItem item3 = FlyweightMapFactory.getFlyweightMapFactory().getItem(new MapItem("白楊")); FlyweightItem item4 = FlyweightMapFactory.getFlyweightMapFactory().getItem(new MapItem("楓樹")); item1.draw(); item2.draw(); item3.draw(); item4.draw(); System.out.println("享元中擁有的物件數量為:"); System.out.println(FlyweightMapFactory.getFlyweightMapFactory().getItemCount()); } }
假設我們正在做一個地圖編輯器,所有的地圖元素都是MapItem類,其中的name表示這個地圖元素的名稱,我們用它來判斷是否兩個元素為同一元素。MapItem物件可作為引數傳給FlyweightMapItem物件,FlyweightMapItem有一個draw方法,表示把這個元素繪製出來。FlyweightMapFactory是用於判斷元素唯一性的一個工廠類,它內部有一個Map來記錄元素是否已經生產過,在呼叫getItem時,如果物件已經生產過,則直接返回生產的物件,否則,新建一個物件,並將它加入Map中然後返回。由於工廠在本例中必須唯一,因此它採用的是單例模式。
最後,輸出的結果為:
正在繪製楓樹
正在繪製楓樹
正在繪製白楊
正在繪製楓樹
享元中擁有的物件數量為:
2
可見,雖然我們建立了item1、item2、item3、item4,但是由於item1、item2、item4都是楓樹,在享元模式中,它們共享著同一個物件,因此有item1==item2==item4,那麼,整個系統中,真正擁有的物件數量也就只有2個了,分別是楓樹和白楊。
以上便是享元模式的一個簡單應用。享元模式會增加程式的複雜程度,但是會增加程式的效率(在上面的例子中,雖然新建了MapItem物件,但是它很快就被垃圾回收了)。享元模式的應用有:Java的String型別(如String a= "123"; String b="123"; 最後發現a==b,說明Java的String型別是享元模式的),以及文書處理系統中字元的處理方法(這個是顯然的,假設一篇文章有10萬個字,不可能建立10萬個物件的,它需要用到享元模式來減少開支)。享元模式通常包含共享部分和非共享部分,非共享部分通常作為共享部分的父節點。