cover

你看過這種 constructor 嗎?

new Report('Monthly Sales', true, false, 'bar', null, 'auto', 'Generated');

第三個參數是什麼?第五個 null 代表什麼意思?沒有人知道。連寫的人三天後也不知道了。

先講結論

Builder Pattern 把物件的建立過程拆成一連串有意義的步驟。每一步都有明確的 method 名稱,最後呼叫 build() 產出成品。程式碼自帶文件,一看就懂。

classDiagram
    class ReportDirector {
        -builder : ReportBuilder
        +buildMonthlyReport() Report
    }
    class ReportBuilder {
        -report : Report
        +setTitle(title) ReportBuilder
        +addChart(chart) ReportBuilder
        +addTable(table) ReportBuilder
        +setNotes(notes) ReportBuilder
        +build() Report
    }
    class Report {
        -title : String
        -charts : Array
        -tables : Array
        -notes : String
    }
    ReportDirector --> ReportBuilder : 指揮建立流程
    ReportBuilder --> Report : 產出

用 Builder 重寫

Builder 模式:分步組裝報表

class Report {
    constructor() {
        this.title = '';
        this.charts = [];
        this.tables = [];
        this.notes = '';
    }
}
 
class ReportBuilder {
    constructor() { this.report = new Report(); }
 
    setTitle(title) {
        this.report.title = title;
        return this; // return this 才能 chain
    }
 
    addChart(chart) {
        this.report.charts.push(chart);
        return this;
    }
 
    addTable(table) {
        this.report.tables.push(table);
        return this;
    }
 
    setNotes(notes) {
        this.report.notes = notes;
        return this;
    }
 
    build() { return this.report; }
}
 
// 看這段 code,你不用看文件就知道在建什麼
const report = new ReportBuilder()
    .setTitle('Monthly Sales Report')
    .addChart({ type: 'bar', data: [100, 200, 150] })
    .addTable({ headers: ['Product', 'Sales'], rows: [] })
    .setNotes('Generated automatically')
    .build();

每個步驟都有名字,順序隨你排,不需要的步驟可以跳過。比起那串看不懂的 constructor 參數,這段 code 根本是自帶說明書。

Director:預設組合

如果某種 Report 組合很常用,可以用 Director 封裝起來:

class ReportDirector {
    constructor(builder) { this.builder = builder; }
 
    buildMonthlyReport() {
        return this.builder
            .setTitle('Monthly Sales Report')
            .addChart({ type: 'bar', data: [100, 200, 150] })
            .setNotes('Auto-generated')
            .build();
    }
}

但說實話,在 JavaScript 裡 Director 不一定需要——直接寫一個 factory function 也能達到同樣效果。Builder Pattern 的核心價值在 Builder 本身,不在 Director。

什麼時候用 Builder?

  • Constructor 參數超過 4 個
  • 有些參數是可選的
  • 物件的建立有明確的步驟

如果你的物件就三個欄位,直接用 constructor 或 object literal 就好。不用每個東西都 Builder 一下。


Builder Pattern 就像訂製漢堡——你一步步選麵包、選肉排、選醬料,最後拿到你要的。比起「請給我第 37 號套餐」,你至少知道自己在吃什麼。


延伸閱讀