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,這就是典型的組合模式的運用。