在現實生活中,存在很多“部分-整體”的關係,例如,大學中的部門與學院、總公司中的部門與分公司、學習用品中的書與書包、生活用品中的衣月艮與衣櫃以及廚房中的鍋碗瓢盆等。在軟件開發中也是這樣,例如,文件系統中的文件與文件夾、窗體程序中的簡單控件與容器控件等。對這些簡單對象與複合對象的處理,如果用組合模式來實現會很方便。
1. 組合模式的定義與特點
組合(Composite)模式的定義:有時又叫作部分-整體模式,它是一種將對象組合成樹狀的層次結構的模式,用來表示“部分-整體”的關係,使用戶對單個對象和組合對象具有一致的訪問性。
組合模式的主要優點有:
- 組合模式使得客戶端代碼可以一致地處理單個對象和組合對象,無須關心自己處理的是單個對象,還是組合對象,這簡化了客戶端代碼;
- 更容易在組合體內加入新的對象,客戶端不會因爲加入了新的對象而更改源代碼,滿足“開閉原則”;
其主要缺點是:
- 設計較複雜,客戶端需要花更多時間理清類之間的層次關係;
- 不容易限制容器中的構件;
- 不容易用繼承的方法來增加構件的新功能;
2. 組合模式的結構與實現
組合模式的結構不是很複雜,下面對它的結構和實現進行分析。
2.1 模式的結構
組合模式包含以下主要角色。
- 抽象構件(Component)角色:它的主要作用是爲樹葉構件和樹枝構件聲明公共接口,並實現它們的默認行爲。在透明式的組合模式中抽象構件還聲明訪問和管理子類的接口;在安全式的組合模式中不聲明訪問和管理子類的接口,管理工作由樹枝構件完成。
- 樹葉構件(Leaf)角色:是組合中的葉節點對象,它沒有子節點,用於實現抽象構件角色中 聲明的公共接口。
- 樹枝構件(Composite)角色:是組合中的分支節點對象,它有子節點。它實現了抽象構件角色中聲明的接口,它的主要作用是存儲和管理子部件,通常包含 Add()、Remove()、GetChild() 等方法。
組合模式分爲透明式的組合模式和安全式的組合模式。
(1) 透明方式:在該方式中,由於抽象構件聲明瞭所有子類中的全部方法,所以客戶端無須區別樹葉對象和樹枝對象,對客戶端來說是透明的。但其缺點是:樹葉構件本來沒有 Add()、Remove() 及 GetChild() 方法,卻要實現它們(空實現或拋異常),這樣會帶來一些安全性問題。其結構圖如圖 1 所示。
(2) 安全方式:在該方式中,將管理子構件的方法移到樹枝構件中,抽象構件和樹葉構件沒有對子對象的管理方法,這樣就避免了上一種方式的安全性問題,但由於葉子和分支有不同的接口,客戶端在調用時要知道樹葉對象和樹枝對象的存在,所以失去了透明性。其結構圖如圖 2 所示。
2.2 模式的實現
假如要訪問集合 c0={leaf1,{leaf2,leaf3}} 中的元素,其對應的樹狀圖如圖 3 所示。
下面給出透明式的組合模式的實現代碼,與安全式的組合模式的實現代碼類似,只要對其做簡單修改就可以了。
package composite;
import java.util.ArrayList;
public class CompositePattern
{
public static void main(String[] args)
{
Component c0=new Composite();
Component c1=new Composite();
Component leaf1=new Leaf("1");
Component leaf2=new Leaf("2");
Component leaf3=new Leaf("3");
c0.add(leaf1);
c0.add(c1);
c1.add(leaf2);
c1.add(leaf3);
c0.operation();
}
}
//抽象構件
interface Component
{
public void add(Component c);
public void remove(Component c);
public Component getChild(int i);
public void operation();
}
//樹葉構件
class Leaf implements Component
{
private String name;
public Leaf(String name)
{
this.name=name;
}
public void add(Component c){ }
public void remove(Component c){ }
public Component getChild(int i)
{
return null;
}
public void operation()
{
System.out.println("樹葉"+name+":被訪問!");
}
}
//樹枝構件
class Composite implements Component
{
private ArrayList<Component> children=new ArrayList<Component>();
public void add(Component c)
{
children.add(c);
}
public void remove(Component c)
{
children.remove(c);
}
public Component getChild(int i)
{
return children.get(i);
}
public void operation()
{
for(Object obj:children)
{
((Component)obj).operation();
}
}
}
程序運行結果如下:
樹葉1:被訪問! 樹葉2:被訪問! 樹葉3:被訪問!
3. 組合模式的應用實例
【例1】用組合模式實現當用戶在商店購物後,顯示其所選商品信息,並計算所選商品總價的功能。
說明:假如李先生到韶關“天街e角”生活用品店購物,用 1 個紅色小袋子裝了 2 包婺源特產(單價 7.9 元)、1 張婺源地圖(單價 9.9 元);用 1 個白色小袋子裝了 2 包韶關香藉(單價 68 元)和 3 包韶關紅茶(單價 180 元);用 1 箇中袋子裝了前面的紅色小袋子和 1 個景德鎮瓷器(單價 380 元);用 1 個大袋子裝了前面的中袋子、白色小袋子和 1 雙李寧牌運動鞋(單價 198 元)。
最後“大袋子”中的內容有:{1 雙李寧牌運動鞋(單價 198 元)、白色小袋子{2 包韶關香菇(單價 68 元)、3 包韶關紅茶(單價 180 元)}、中袋子{1 個景德鎮瓷器(單價 380 元)、紅色小袋子{2 包婺源特產(單價 7.9 元)、1 張婺源地圖(單價 9.9 元)}}},現在要求編程顯示李先生放在大袋子中的所有商品信息並計算要支付的總價。
本實例可按安全組合模式設計,其結構圖如圖 4 所示。
程序代碼如下:
package composite;
import java.util.ArrayList;
public class ShoppingTest
{
public static void main(String[] args)
{
float s=0;
Bags BigBag,mediumBag,smallRedBag,smallWhiteBag;
Goods sp;
BigBag=new Bags("大袋子");
mediumBag=new Bags("中袋子");
smallRedBag=new Bags("紅色小袋子");
smallWhiteBag=new Bags("白色小袋子");
sp=new Goods("婺源特產",2,7.9f);
smallRedBag.add(sp);
sp=new Goods("婺源地圖",1,9.9f);
smallRedBag.add(sp);
sp=new Goods("韶關香菇",2,68);
smallWhiteBag.add(sp);
sp=new Goods("韶關紅茶",3,180);
smallWhiteBag.add(sp);
sp=new Goods("景德鎮瓷器",1,380);
mediumBag.add(sp);
mediumBag.add(smallRedBag);
sp=new Goods("李寧牌運動鞋",1,198);
BigBag.add(sp);
BigBag.add(smallWhiteBag);
BigBag.add(mediumBag);
System.out.println("您選購的商品有:");
BigBag.show();
s=BigBag.calculation();
System.out.println("要支付的總價是:"+s+"元");
}
}
//抽象構件:物品
interface Articles
{
public float calculation(); //計算
public void show();
}
//樹葉構件:商品
class Goods implements Articles
{
private String name; //名字
private int quantity; //數量
private float unitPrice; //單價
public Goods(String name,int quantity,float unitPrice)
{
this.name=name;
this.quantity=quantity;
this.unitPrice=unitPrice;
}
public float calculation()
{
return quantity*unitPrice;
}
public void show()
{
System.out.println(name+"(數量:"+quantity+",單價:"+unitPrice+"元)");
}
}
//樹枝構件:袋子
class Bags implements Articles
{
private String name; //名字
private ArrayList<Articles> bags=new ArrayList<Articles>();
public Bags(String name)
{
this.name=name;
}
public void add(Articles c)
{
bags.add(c);
}
public void remove(Articles c)
{
bags.remove(c);
}
public Articles getChild(int i)
{
return bags.get(i);
}
public float calculation()
{
float s=0;
for(Object obj:bags)
{
s+=((Articles)obj).calculation();
}
return s;
}
public void show()
{
for(Object obj:bags)
{
((Articles)obj).show();
}
}
}
程序運行結果如下:
您選購的商品有: 李寧牌運動鞋(數量:1,單價:198.0元) 韶關香菇(數量:2,單價:68.0元) 韶關紅茶(數量:3,單價:180.0元) 景德鎮瓷器(數量:1,單價:380.0元) 婺源特產(數量:2,單價:7.9元) 婺源地圖(數量:1,單價:9.9元) 要支付的總價是:1279.7元
4. 組合模式的應用場景
前面分析了組合模式的結構與特點,下面分析它適用的以下應用場景。
- 在需要表示一個對象整體與部分的層次結構的場合。
- 要求對用戶隱藏組合對象與單個對象的不同,用戶可以用統一的接口使用組合結構中的所有對象的場合。
5. 組合模式的擴展
如果對前面介紹的組合模式中的樹葉節點和樹枝節點進行抽象,也就是說樹葉節點和樹枝節點還有子節點,這時組合模式就擴展成複雜的組合模式了,如 Java AWT/Swing 中的簡單組件 JTextComponent 有子類 JTextField、JTextArea,容器組件 Container 也有子類 Window、Panel。複雜的組合模式的結構圖如圖 5 所示。