物件以樹狀結構組合,做為表現出整體-部分的階層關係。
Composite Pattern使得使用者對單個物件和組合物件的使用具有一致性,所以又稱部分-整體模式。拿生活會遇到的例子來講:總公司的部門與分公司、學生的書與書包...等等。
成員 | 功用 |
---|---|
Component 抽象構件 | 為組合中的物件宣告介面,在透明式組合模式中可以宣告包括管理子類的方法接口。安全式組合模式中管理子類的方法則由樹枝構件完成。 |
Leaf 樹葉構件 | 組合中的樹葉節點,沒有子節點。實作Component宣告的接口。 |
Composite 樹枝構件 | 組合中的分支節點,有子節點。實作Component宣告的接口。主要作用在於儲存及管理子類物件。通常有add(), remove()等方法。 |
可以參考下面的UML圖,Composite1內可以有Composite以及Leaf。而Composite2下還可以有其他的節點,但Leaf則不會有。
組合模式有兩種,分別是透明式組合模式以及安全式組合模式。
我們來舉一個範例:最近中秋節要到了,大家都瘋狂地去買烤肉用品。要去大賣場之前,就會提著大包小包的購物袋,衝去賣場裝各種肉類海鮮以及烤肉用具。在這個範例裡面,購物袋就是樹枝構件。而肉類海鮮以及烤肉用具就是樹葉構件。將肉放入(add)或拿出(remove)購物袋的行為,就是管理子構件的方法。
將所有的方法先定義在Component內,如此一來Client不需要區別是Leaf還是Composite,對他來說是透明的。但缺點就是,Leaf並不需要實作add以及remove方法。若這樣的話,勢必只能空實作或是拋錯,這樣會造成一些安全性的疑慮。
將管理子構件的方法移出Component,這樣Component以及Leaf就不會有管理子構件的方法。這樣Leaf就不需要空實作或是拋錯,解決了透明式安全性的疑慮。但由於將方法切開,所以Component與Leaf的接口就會不一樣,Client在呼叫時必須要知道兩者的存在,而失去了透明性。
來沿用剛剛中秋節的例子。大家為了準備烤肉,帶著購物袋去賣場採購。我們先用透明式的組合模式來實作看看。
首先我們先將Component的介面給定義出來:
interface Articles {
public void add(Articles articles);
public void remove(Articles articles);
public float calculation();
public void show();
}
由於我們使用透明組合模式,所以將所有的方法都定義在component內。
接著建立肉類海鮮等等商品的類別Leaf來實作Component:
class Commodity implements Articles {
private String name;
private int quantity;
private int unitPrice;
public Commodity(String name, int quantity, int unitPrice) {
this.name=name;
this.quantity=quantity;
this.unitPrice=unitPrice;
}
public void add(Articles articles) {}
public void remove(Articles articles) {}
public float calculation() {
return quantity * unitPrice;
}
public void show() {
System.out.println(name + " (quantity: "+quantity+", unitPrice: NT "+unitPrice+")");
}
}
產品內有名稱、要購買的數量以及價錢。其中add與remove不需要實作,所以方法內放空的。
下一步就是將購物袋Composite給建立起來:
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 articles){
bags.add(articles);
}
public void remove(Articles articles) {
bags.remove(articles);
}
public float calculation() {
float s = 0;
for(Object bag : bags) {
s += ((Articles)bag).calculation();
}
return s;
}
public void show() {
for(Object bag : bags) {
((Articles)bag).show();
}
}
}
最後來使用看看我們剛建立好的程式:
public class ShoppingTest {
public static void main(String[] args) {
Articles bigBag, smallBag;
Articles commodity;
bigBag = new Bags("Big Bag");
smallBag = new Bags("small Bag");
commodity = new Commodity("bamboo shoot", 5, 20);
smallBag.add(commodity);
commodity = new Commodity("plate", 5, 40);
smallBag.add(commodity);
commodity = new Commodity("meat", 10, 300);
smallBag.add(commodity);
commodity = new Commodity("seafood", 3, 480);
smallBag.add(commodity);
commodity = new Commodity("barbecue grill", 2, 199);
bigBag.add(commodity);
commodity = new Commodity("charcoal",2, 399);
bigBag.add(commodity);
bigBag.add(smallBag);
System.out.println("list:");
bigBag.show();
float amount = bigBag.calculation();
System.out.println("total: NT "+ amount);
}
}
output
list:
barbecue grill (quantity: 2, unitPrice: NT 199)
charcoal (quantity: 2, unitPrice: NT 399)
bamboo shoot (quantity: 5, unitPrice: NT 20)
plate (quantity: 5, unitPrice: NT 40)
meat (quantity: 10, unitPrice: NT 300)
seafood (quantity: 3, unitPrice: NT 480)
total: NT 5936.0
由於是透明組合模式,所以所有的構件繼承的Component內擁有相同的方法。當Client在宣告時不需要管他們是哪種構件,只需要將他們宣告出來即可。而在Leaf中,因為Component定義了所有的方法,所以他必須要空實作add以及remove兩個方法。
我們一樣先將Component的介面給定義出來:
interface Articles {
public float calculation();
public void show();
}
因為現在使用的是安全組合模式,所以add即remove不需要定義在Component內。
接著建立肉類海鮮等等的類別Leaf來實作Component:
class Commodity implements Articles {
private String name;
private int quantity;
private int unitPrice;
public Commodity(String name, int quantity, int unitPrice) {
this.name=name;
this.quantity=quantity;
this.unitPrice=unitPrice;
}
public float calculation() {
return quantity * unitPrice;
}
public void show() {
System.out.println(name + " (quantity: "+quantity+", unitPrice: NT "+unitPrice+")");
}
}
由於Component內已經沒有add與remove了,所以Leaf內也不在需要實作這兩個Function。
下一步就是將購物袋Composite給建立起來:
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 articles){
bags.add(articles);
}
public void remove(Articles articles) {
bags.remove(articles);
}
public float calculation() {
float s = 0;
for(Object bag : bags) {
s += ((Articles)bag).calculation();
}
return s;
}
public void show() {
for(Object bag : bags) {
((Articles)bag).show();
}
}
}
這部分並沒有差別
最後一樣來使用看看我們剛建立好的程式:
public class ShoppingTest {
public static void main(String[] args) {
Bags bigBag, smallBag; // 更改
Commodity commodity; // 更改
bigBag = new Bags("Big Bag");
smallBag = new Bags("small Bag");
commodity = new Commodity("bamboo shoot", 5, 20);
smallBag.add(commodity);
commodity = new Commodity("plate", 5, 40);
smallBag.add(commodity);
commodity = new Commodity("meat", 10, 300);
smallBag.add(commodity);
commodity = new Commodity("seafood", 3, 480);
smallBag.add(commodity);
commodity = new Commodity("barbecue grill", 2, 199);
bigBag.add(commodity);
commodity = new Commodity("charcoal",2, 399);
bigBag.add(commodity);
bigBag.add(smallBag);
System.out.println("list:");
bigBag.show();
float amount = bigBag.calculation();
System.out.println("total: NT "+ amount);
}
}
output
list:
barbecue grill (quantity: 2, unitPrice: NT 199)
charcoal (quantity: 2, unitPrice: NT 399)
bamboo shoot (quantity: 5, unitPrice: NT 20)
plate (quantity: 5, unitPrice: NT 40)
meat (quantity: 10, unitPrice: NT 300)
seafood (quantity: 3, unitPrice: NT 480)
total: NT 5936.0
跑出來的結果並沒有差異,安全組合模式差在Articles(Component)內的add與remove兩個方法,換到Bags(Composite)內去做了。所以Commodity(Leaf)也不需要去空實作這兩個方法。而Client內的宣告,因為兩者內的方法不在一樣,所以從Articles改成Commodity以及Bags。
《設計模式》一書認為:在組合模式中,相對於安全性,我們比較強調透明性。對於透明式的組合模式中的Leaf內不需要的方法可以使用空處理或者異常報告的方式來解決。
將元件做分離,依照需求一一組裝起來,建立不同需求的物件。
Component 抽象構件:
Leaf 樹葉構件:
Composite 樹枝構件:
優點
1. 容易擴充,不需要應未加入新的物件而改變程式碼。
2. 高層模組呼叫容易。
3. 節點自由增加。
缺點
1. 設計較複雜。
2. 不容易用繼承的方法來增加構件的功能
1. 表示物件的部分-整體層次結構。
2. 希望使用者忽略組合物件與單個物件的不同,使用者將統一地使用組合結構中的所有物件。
组合模式(详解版)
組合模式(Composite Pattern)(一):組合模式介紹
组合模式