1. 程式人生 > >Java設計模式之從[Dota地圖]分析享元(Flyweight)模式

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萬個物件的,它需要用到享元模式來減少開支)。享元模式通常包含共享部分和非共享部分,非共享部分通常作為共享部分的父節點。