1. 程式人生 > >Java設計模式之從[魔獸世界包裹系統]分析組合(Composite)模式

Java設計模式之從[魔獸世界包裹系統]分析組合(Composite)模式

  RPG遊戲中的包裹(或者稱為揹包)是玩家攜帶物品的地方,它的大小決定著玩家能夠攜帶物品數量。如在魔獸世界中,玩家起初的物品欄(將物品欄視為玩家的唯一一個包裹)的格子很少,但是玩家可以將新的包裹放在物品欄中,達到擴充物品欄的效果。也就是說,物品欄可以放消耗品、武器等零散的物品,當然也可以放包裹。

  假定魔獸世界有如下設定:玩家一開始擁有一個預設的包裹(物品欄),玩家可以獲得新的包裹放在物品欄上。包裹可以存放武器、防具、消耗品,以及包裹。它可以無限次的迭代,形成樹狀,如下所示:

  以上的模型反應了組合(Composite)模式的思維。在上述的樹中,我們把節點分為兩種:部分和整體。所謂“部分”,就是指那些沒有子節點的項,如爐石、匕首等;整體是指擁有子節點的項,它可以儲存子節點。組合模式的意圖是將物件組成樹形結構以表示“部分-整體”的層次結構,使得使用者對單個物件和組合物件的使用具有一致性。

  下面是實現這個機制的Java程式碼:

import java.util.ArrayList;

interface WowItem {
    String getName();
    void add(WowItem item);
    void remove(WowItem item);
    WowItem get(int i);
    void print();
}

class Bag implements WowItem {
    ArrayList<WowItem> items = new ArrayList<WowItem>();
    public String getName(){
        return "揹包 ";
    }
    public void add(WowItem item){
        items.add(item);
    }
    public void remove(WowItem item){
        items.remove(item);
    }
    public WowItem get(int i){
        return items.get(i);
    }
    public void print(){
        System.out.println(getName() + "{" );
        for ( WowItem i : items){
            i.print();
        }
        System.out.println("}");
    }
}

class Hearthstone implements WowItem {
    public String getName(){
        return "爐石";
    }
    public void add(WowItem item){ }
    public void remove(WowItem item){ }
    public WowItem get(int i){ return null; }
    public void print(){
        System.out.println(getName());
    }
}

class Dagger implements WowItem {
    public String getName(){
        return "匕首";
    }
    public void add(WowItem item){ }
    public void remove(WowItem item){ }
    public WowItem get(int i){ return null; }
    public void print(){
        System.out.println(getName());
    }
}


class Composite
{
    public static void main(String[] args) {
        WowItem myBag = new Bag();
        WowItem smallBag = new Bag();
        myBag.add(new Dagger());
        myBag.add(smallBag);
        smallBag.add(new Dagger());
        myBag.add(new Hearthstone());
        myBag.print();
    }
}

  為了簡化期間,我們構造三個物品:包裹、匕首和爐石。其中,包裹可以容納子物品,匕首和爐石不可以。為了保持一致性(能在同一容器中容納它們),它們必須繼承於同一個類或者介面,在這個例子上它們均繼承於WowItem介面。這個介面聲明瞭4個方法,分別是表示新增、刪除、獲取某一子物品、列印本物品名稱。有幾點需要注意:1、我採用了ArrayList容器來儲存WowItem,因此所有物品必須繼承WowItem。2、在Bag(包裹)類中,均有對add、remove和get做實現,Bag類中的print會呼叫其所有子節點的print方法。3、對於非Bag類,如Dagger、Hearthstone,它們沒有子節點,因此對add、remove、get做了空實現(get返回了null),print方法則直接打印出了它們的名字。

  在main方法中,我們定義了個根節點myBag,為它增加了一把匕首Dagger、一個小揹包(smallBag),在小揹包中添加了一把匕首,然後再在myBag中添加了一個爐石,程式執行結果如下:

揹包 {
匕首
揹包 {
匕首
}
爐石
}

  最後再說兩個問題:

  一、你可能會發現,對於非組合類(Dagger、Hearthstone),有許多程式碼是多餘的,例如它們不需要實現add、remove、get等方法,如果這些類均繼承於WowItem,程式碼編寫會比較繁瑣(因為要每個繼承的方法都要空實現),而且會損失安全性——我們可能一不小心為一個Dagger呼叫了add方法或者remove方法。因此,你可以選擇為這些非組合類建立一個另外一個類來管理這些子部件,這樣的話,就喪失了透明性,它讓部分和整體變成了兩個獨立的部分,我是不推薦這麼做的。改善的方法有很多,例如非組合類都繼承於一個抽象類A,而這個抽象類A是繼承於WowItem的,並且實現了add、remove、get方法,均為丟擲一個異常。那麼,只要是非組合類呼叫了add、remove或get,均會得到一個異常。

  二、組合在實際的使用中用途非常廣。如在編寫介面的時候,用到的控制元件機制——有些控制元件可以當做“容器”,例如,Frame中可以放入Button、RadioButton,也可以放入Frame,這就是典型的組合模式的運用。