目錄
組合模式的定義與特點
組合模式的結構與實作
組合模式的應用執行個體
組合模式的應用場景
組合模式的擴充
樹形結構在軟體中随處可見,例如作業系統中的目錄結構、應用軟體中的菜單、辦公系統中的公司組織結構等等,如何運用面向對象的方式來處理這種樹形結構是組合模式需要解決的問題,組合模式通過一種巧妙的設計方案使得使用者可以一緻性地處理整個樹形結構或者樹形結構的一部分,也可以一緻性地處理樹形結構中的葉子節點(不包含子節點的節點)和容器節點(包含子節點的節點)。下面将學習這種用于處理樹形結構的組合模式。
組合模式的定義與特點
組合(Composite)模式的定義:有時又叫作部分-整體模式,它是一種将對象組合成樹狀的層次結構的模式,用來表示“部分-整體”的關系,使使用者對單個對象群組合對象具有一緻的通路性。
組合模式的主要優點有:
(1) 組合模式可以清楚地定義分層次的複雜對象,表示對象的全部或部分層次,它讓用戶端忽略了層次的差異,友善對整個層次結構進行控制。
(2) 用戶端可以一緻地使用一個組合結構或其中單個對象,不必關心處理的是單個對象還是整個組合結構,簡化了用戶端代碼。
(3) 在組合模式中增加新的容器構件和葉子構件都很友善,無須對現有類庫進行任何修改,符合“開閉原則”。
(4) 組合模式為樹形結構的面向對象實作提供了一種靈活的解決方案,通過葉子對象和容器對象的遞歸組合,可以形成複雜的樹形結構,但對樹形結構的控制卻非常簡單。
其主要缺點是:
在增加新構件時很難對容器中的構件類型進行限制。有時候我們希望一個容器中隻能有某些特定類型的對象,例如在某個檔案夾中隻能包含文本檔案,使用組合模式時,不能依賴類型系統來施加這些限制,因為它們都來自于相同的抽象層,在這種情況下,必須通過在運作時進行類型檢查來實作,這個實作過程較為複雜。
組合模式的結構與實作
組合模式的結構不是很複雜,下面對它的結構和實作進行分析。
1. 模式的結構
組合模式包含以下主要角色。
- 抽象構件(Component)角色:它的主要作用是為樹葉構件和樹枝構件聲明公共接口,并實作它們的預設行為。在透明式的組合模式中抽象構件還聲明通路和管理子類的接口;在安全式的組合模式中不聲明通路和管理子類的接口,管理工作由樹枝構件完成。
- 樹葉構件(Leaf)角色:是組合中的葉節點對象,它沒有子節點,用于實作抽象構件角色中 聲明的公共接口。
- 樹枝構件(Composite)角色:是組合中的分支節點對象,它有子節點。它實作了抽象構件角色中聲明的接口,它的主要作用是存儲和管理子部件,通常包含 Add()、Remove()、GetChild() 等方法。
組合模式分為透明式的組合模式和安全式的組合模式。
(1) 透明方式:在該方式中,由于抽象構件聲明了所有子類中的全部方法,是以用戶端無須差別樹葉對象和樹枝對象,對用戶端來說是透明的。但其缺點是:樹葉構件本來沒有 Add()、Remove() 及 GetChild() 方法,卻要實作它們(空實作或抛異常),這樣會帶來一些安全性問題。其結構圖如圖 1 所示。
圖1 透明式的組合模式的結構圖
(2) 安全方式:在該方式中,将管理子構件的方法移到樹枝構件中,抽象構件和樹葉構件沒有對子對象的管理方法,這樣就避免了上一種方式的安全性問題,但由于葉子和分支有不同的接口,用戶端在調用時要知道樹葉對象和樹枝對象的存在,是以失去了透明性。其結構圖如圖 2 所示。
圖2 安全式的組合模式的結構圖
2. 模式的實作
假如要通路集合 c0={leaf1,{leaf2,leaf3}} 中的元素,其對應的樹狀圖如圖 3 所示。
圖3 集合c0的樹狀圖
下面給出透明式的組合模式的實作代碼,與安全式的組合模式的實作代碼類似,隻要對其做簡單修改就可以了。
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:被通路!
組合模式的應用執行個體
【例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 所示。
圖4 韶關“天街e角”店購物的結構圖
程式代碼如下:
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元
組合模式的應用場景
前面分析了組合模式的結構與特點,下面分析它适用的以下應用場景。
- 在需要表示一個對象整體與部分的層次結構的場合。
- 要求對使用者隐藏組合對象與單個對象的不同,使用者可以用統一的接口使用組合結構中的所有對象的場合。
組合模式的擴充
如果對前面介紹的組合模式中的樹葉節點和樹枝節點進行抽象,也就是說樹葉節點和樹枝節點還有子節點,這時組合模式就擴充成複雜的組合模式了,如Java AWT/Swing 中的簡單元件 JTextComponent 有子類 JTextField、JTextArea,容器元件 Container 也有子類 Window、Panel。複雜的組合模式的結構圖如圖 5 所示。