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,这就是典型的组合模式的运用。