組合模式將對象組合成樹形結構以表示「部分-整體」層次關係,使得客戶端代碼對單個對象(葉子)和組合對象(容器)的使用具有一致性。
add/remove/getChild,遞歸調用子節點操作
// 抽象構件
abstract class MenuComponent {
public void add(MenuComponent c) { throw new UnsupportedOperationException(); }
public void remove(MenuComponent c) { throw new UnsupportedOperationException(); }
public MenuComponent getChild(int i) { throw new UnsupportedOperationException(); }
public String getName() { throw new UnsupportedOperationException(); }
public String getDescription() { throw new UnsupportedOperationException(); }
public double getPrice() { throw new UnsupportedOperationException(); }
public boolean isVegetarian() { throw new UnsupportedOperationException(); }
public abstract void print();
}
// 葉子:菜單項
class MenuItem extends MenuComponent {
private String name, description; private boolean vegetarian; private double price;
public MenuItem(String n, String d, boolean v, double p) { ... }
public String getName() { return name; }
public String getDescription() { return description; }
public double getPrice() { return price; }
public boolean isVegetarian() { return vegetarian; }
public void print() {
System.out.print(" " + getName());
if (isVegetarian()) System.out.print("(v)");
System.out.println(", " + getPrice() + " -- " + getDescription());
}
}
// 容器:菜單
class Menu extends MenuComponent {
private List<MenuComponent> items = new ArrayList<>();
private String name, description;
public Menu(String n, String d) { name=n; description=d; }
public void add(MenuComponent c) { items.add(c); }
public void remove(MenuComponent c) { items.remove(c); }
public MenuComponent getChild(int i) { return items.get(i); }
public String getName() { return name; }
public String getDescription() { return description; }
public void print() {
System.out.println("\n" + getName() + ", " + getDescription());
System.out.println("---------------------");
for (MenuComponent c : items) c.print(); // 遞歸調用
}
}
// 客戶端:服務員
class Waitress {
private MenuComponent allMenus;
public Waitress(MenuComponent menus) { allMenus = menus; }
public void printMenu() { allMenus.print(); }
}
| 優點 | 缺點 |
|---|---|
| ① 簡化客戶端代碼,統一處理葉子與容器 | ① 設計過於通用,可能導致類層次過深 |
| ② 易於添加新類型的構件 | ② 難以限制容器中允許的子構件類型 |
| ③ 符合開閉原則 | ③ 在功能差異大的葉子/容器間強行統一接口可能不自然 |
組合模式是處理樹形結構的利器。關鍵在於定義良好的 Component 接口,讓葉子與容器共享公共行為,並在容器中遞歸委派操作。菜單系統、文件目錄、組織架構圖都是其典型應用場景。