iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 17
0

本文同步分享於個人blog

  • 定義


物件以樹狀結構組合,做為表現出整體-部分的階層關係。

Composite Pattern使得使用者對單個物件和組合物件的使用具有一致性,所以又稱部分-整體模式。拿生活會遇到的例子來講:總公司的部門與分公司、學生的書與書包...等等。

  • Composite Pattern 成員


成員 功用
Component 抽象構件 為組合中的物件宣告介面,在透明式組合模式中可以宣告包括管理子類的方法接口。安全式組合模式中管理子類的方法則由樹枝構件完成。
Leaf 樹葉構件 組合中的樹葉節點,沒有子節點。實作Component宣告的接口。
Composite 樹枝構件 組合中的分支節點,有子節點。實作Component宣告的接口。主要作用在於儲存及管理子類物件。通常有add(), remove()等方法。

可以參考下面的UML圖,Composite1內可以有Composite以及Leaf。而Composite2下還可以有其他的節點,但Leaf則不會有。

cp1

  • 透明式的組合模式 v.s 安全式的組合模式


組合模式有兩種,分別是透明式組合模式以及安全式組合模式。

我們來舉一個範例:最近中秋節要到了,大家都瘋狂地去買烤肉用品。要去大賣場之前,就會提著大包小包的購物袋,衝去賣場裝各種肉類海鮮以及烤肉用具。在這個範例裡面,購物袋就是樹枝構件。而肉類海鮮以及烤肉用具就是樹葉構件。將肉放入(add)或拿出(remove)購物袋的行為,就是管理子構件的方法。

透明式的組合模式

將所有的方法先定義在Component內,如此一來Client不需要區別是Leaf還是Composite,對他來說是透明的。但缺點就是,Leaf並不需要實作add以及remove方法。若這樣的話,勢必只能空實作或是拋錯,這樣會造成一些安全性的疑慮。

cp2

安全式的組合模式

將管理子構件的方法移出Component,這樣Component以及Leaf就不會有管理子構件的方法。這樣Leaf就不需要空實作或是拋錯,解決了透明式安全性的疑慮。但由於將方法切開,所以Component與Leaf的接口就會不一樣,Client在呼叫時必須要知道兩者的存在,而失去了透明性。

cp3

  • Composite Pattern 實作


來沿用剛剛中秋節的例子。大家為了準備烤肉,帶著購物袋去賣場採購。我們先用透明式的組合模式來實作看看。

透明式的組合模式實作

首先我們先將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內不需要的方法可以使用空處理或者異常報告的方式來解決。

Composite Pattern的目標
將元件做分離,依照需求一一組裝起來,建立不同需求的物件。
Composite Pattern的成員
Component 抽象構件:
Leaf 樹葉構件:
Composite 樹枝構件:
Composite Pattern的優缺點
優點
1. 容易擴充,不需要應未加入新的物件而改變程式碼。
2. 高層模組呼叫容易。
3. 節點自由增加。
缺點
1. 設計較複雜。
2. 不容易用繼承的方法來增加構件的功能
Composite Pattern的使用時機
1. 表示物件的部分-整體層次結構。
2. 希望使用者忽略組合物件與單個物件的不同,使用者將統一地使用組合結構中的所有物件。
  • 範例程式碼


範例1:實作透明式的組合模式
範例2:實作安全式的組合模式

  • References


组合模式(详解版)
組合模式(Composite Pattern)(一):組合模式介紹
组合模式


上一篇
[Day16] 橋接模式 | Bridge Pattern
下一篇
[Day18] 裝飾者模式 | Decorator Pattern
系列文
從生活中認識Design Pattern30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言