
你用過 ls -R 或 Windows 的 tree 指令嗎?不管是檔案還是資料夾,它們都能被「顯示」。資料夾裡面可能有更多資料夾和檔案,但對外的操作介面是一樣的。
這就是 Composite Pattern。
先講結論
Composite 把物件組成樹狀結構,讓「單一物件」和「物件組合」共用同一個介面。使用端不需要區分自己在操作的是一個葉子還是整棵子樹。
classDiagram class ProductCategory { <<abstract>> -name : String +display(indent)* void } class Product { -price : Number +display(indent) void } class Category { -children : ProductCategory[] +add(component) void +remove(component) void +display(indent) void } ProductCategory <|-- Product ProductCategory <|-- Category Category o-- ProductCategory : children
實戰:產品分類樹

class Product {
constructor(name, price) {
this.name = name;
this.price = price;
}
display(indent = 0) {
console.log(`${' '.repeat(indent)}- ${this.name}: $${this.price}`);
}
}
class Category {
constructor(name) {
this.name = name;
this.children = [];
}
add(component) { this.children.push(component); }
remove(component) {
const i = this.children.indexOf(component);
if (i > -1) this.children.splice(i, 1);
}
display(indent = 0) {
console.log(`${' '.repeat(indent)}+ ${this.name}`);
this.children.forEach(child => child.display(indent + 2));
}
}
const electronics = new Category('Electronics');
const phones = new Category('Phones');
phones.add(new Product('iPhone 15', 999));
phones.add(new Product('Samsung S24', 899));
electronics.add(phones);
electronics.add(new Product('MacBook Pro', 2499));
electronics.display();
// + Electronics
// + Phones
// - iPhone 15: $999
// - Samsung S24: $899
// - MacBook Pro: $2499不管是 Product(葉子)還是 Category(容器),都有 display() 方法。呼叫端不用管它是什麼——直接 display() 就對了。
哪裡能看到 Composite?
- React 元件樹:每個元件可以包含其他元件,render 的時候遞迴處理
- DOM 結構:
div裡面可以有div,也可以有span,但你對它們做remove()的方式是一樣的 - 檔案系統:File 和 Directory 的操作介面一致
什麼時候不該用?
如果你的結構是扁平的(沒有巢狀),用陣列就好了。Composite 是為了處理「樹」的場景——沒有樹,就不需要 Composite。
Composite Pattern 就像俄羅斯套娃——打開一個裡面還有一個,但不管哪一層,長相都一樣。只是你永遠不知道裡面還有幾層。