定義
組合(Composite)模式:又稱作部分整體模式,它是一種將對象組合成樹狀的層次結構的模式,用來表示 “部分-整體” 的關係,使用戶對單個對象和組合對象具有一致的訪問性。
組合模式屬於對象結構型模式。
要點
組合模式的優點:
- 組合模式使得客戶端代碼可以一致地處理單個對象和組合對象,無須關心自己處理的是單個對象,還是組合對象,這簡化了客戶端代碼;
- 更容易在組合體內加入新的對象,客戶端不會因爲加入了新的對象而更改源代碼,滿足“開閉原則”;
組合模式的缺點:
- 設計較複雜,客戶端需要花更多時間理清類之間的層次關係;
- 不容易限制容器中的構件;
- 不容易用繼承的方法來增加構件的新功能;
組合模式包含以下主要角色:
抽象構件(Component):它的主要作用是爲樹葉構件和樹枝構件聲明公共接口,並實現它們的默認行爲。在透明式的組合模式中抽象構件還聲明訪問和管理子類的接口;在安全式的組合模式中不聲明訪問和管理子類的接口,管理工作由樹枝構件完成。
樹葉構件(Leaf):是組合中的葉節點對象,它沒有子節點,用於實現抽象構件角色中聲明的公共接口。
樹枝構件(Composite):是組合中的分支節點對象,它有子節點。它實現了抽象構件角色中聲明的接口,它的主要作用是存儲和管理子部件,通常包含 Add()、Remove()、GetChild() 等方法。
例如,要一個連鎖飯店有三個餐廳 —— 煎餅屋、正式餐廳和咖啡廳,它們各自有自己的菜單和菜單項,每個菜單還可能包含子菜單:
場景
假如李先生到超市購物,用 1 個紅色小袋子裝了 2 包純牛奶(單價 7.9 元)、1 袋精品食鹽(單價 9.9 元);用 1 個白色小袋子裝了 2 瓶白酒(單價 68 元)和 3 包龍井茶(單價 80 元);用 1 箇中號藍袋子裝了前面的紅色小袋子和 1 個名牌電炒鍋(單價 380 元);用 1 個大號黑袋子裝了前面的中號藍袋子、白色小袋子和 1 雙運動鞋(單價 198 元),要求編程顯示李先生放在大袋子中的所有商品信息並計算要支付的總價。
實現
Item
/**
* 抽象物品,相當於 Component
*/
public interface Item {
/**
* 計算物品價格
*/
double calculate();
/**
* 展示物品詳情
*/
void show();
}
Goods
/**
* 商品,相當於葉子節點
*/
public class Goods implements Item {
/**
* 商品名稱
*/
private String goodsName;
/**
* 商品單價
*/
private double price;
/**
* 商品數量
*/
private int quantity;
public Goods(String goodsName, double price, int quantity) {
this.goodsName = goodsName;
this.price = price;
this.quantity = quantity;
}
@Override
public double calculate() {
return price * quantity;
}
@Override
public void show() {
System.out.println(String.format("%s (數量:%d,單價:%f)", goodsName, quantity, price));
}
}
Bag
/**
* 袋子,相當於樹枝節點
*/
public class Bag implements Item{
/**
* 袋子名稱
*/
private String bagName;
/**
* 袋子中的商品
*/
private ArrayList<Item> goodsList = new ArrayList<>();
public Bag(String bagName) {
this.bagName = bagName;
}
/**
* 添加商品
*/
public void add(Item item) {
goodsList.add(item);
}
/**
* 刪除商品
*/
public void remove(Item item) {
goodsList.remove(item);
}
/**
* 獲取商品
*/
public Item getChild(int i) {
return goodsList.get(i);
}
@Override
public double calculate() {
float totalPrice = 0;
for(Item item : goodsList) {
totalPrice += item.calculate();
}
return totalPrice;
}
@Override
public void show() {
for (Item item : goodsList) {
item.show();
}
}
}
Client
public class Client {
public static void main(String[] args) {
Bag redSmallBag = new Bag("紅色小袋子");
redSmallBag.add(new Goods("純牛奶", 7.9, 2));
redSmallBag.add(new Goods("精品食鹽", 9.9, 1));
Bag whiteSmallBag = new Bag("白色小袋子");
whiteSmallBag.add(new Goods("白酒", 68, 2));
whiteSmallBag.add(new Goods("龍井茶", 80, 3));
Bag blueMediumBag = new Bag("藍色中號袋子");
blueMediumBag.add(redSmallBag);
blueMediumBag.add(new Goods("名牌電炒鍋", 380, 1));
Bag blackBigBag = new Bag("黑色大號袋子");
blackBigBag.add(blueMediumBag);
blackBigBag.add(whiteSmallBag);
blackBigBag.add(new Goods("運動鞋", 198, 1));
System.out.println(String.format("商品總價是:%f\n", blackBigBag.calculate()));
System.out.println("商品詳情是:");
blackBigBag.show();
}
}
==================================
輸出:
商品總價是:979.700000
商品詳情是:
純牛奶 (數量:2,單價:7.900000)
精品食鹽 (數量:1,單價:9.900000)
名牌電炒鍋 (數量:1,單價:380.000000)
白酒 (數量:2,單價:68.000000)
龍井茶 (數量:3,單價:80.000000)
運動鞋 (數量:1,單價:198.000000)
源碼
總結
- 在需要表示一個對象整體與部分的層次結構的場合
- 要求對用戶隱藏組合對象與單個對象的不同,用戶可以用統一的接口使用組合結構中的所有對象的場合
- 簡化客戶端操作。客戶端只需要面對一致的對象而不用考慮整體部分或者節點葉子的問題
- 具有較強的擴展性。當我們要更改組合對象時,我們只需要調整內部的層次關係,客戶端不用做出任何改動.
- 方便創建出複雜的層次結構。客戶端不用理會組合裏面的組成細節,容易添加節點或者葉子從而創建出複雜的樹形結構
- 需要遍歷組織機構,或者處理的對象具有樹形結構時, 非常適合使用組合模式
- 要求較高的抽象性,如果節點和葉子有很多差異性的話,比如很多方法和屬性都不一樣,不適合使用組合模式
Java AWT/Swing 中的簡單組件 JTextComponent 有子類 JTextField、JTextArea,容器組件 Container 也有子類 Window、Panel。其中涉及到了組合模式。