cover

當你發現自己寫了第三個 if (type === 'xxx') return new XxxPayment() 的時候,就是該用 Factory 的時候了。

先講結論

Factory Pattern 的核心就是:把「決定 new 哪個 class」這件事抽出去。呼叫端只要說「我要信用卡」,工廠就吐出信用卡物件,你不需要知道它怎麼被建出來的。好處?新增支付方式的時候,你不用改呼叫端的程式碼。

你一定遇過這種 code

// 這段 code 每次加新支付方式都要改,而且會越來越長
function createPayment(type, options) {
    if (type === 'credit') return new CreditCardPayment(options);
    if (type === 'paypal') return new PayPalPayment(options);
    if (type === 'bitcoin') return new BitcoinPayment(options);
    // 再加下去就要崩潰了...
}

上面這段程式碼有什麼問題?每加一種支付方式,你就要回來改這個 function。違反開放封閉原則不說,code review 的人也會開始翻白眼。

用 Factory 重構

classDiagram
    class Payment {
        <<abstract>>
        +process(amount)* String
    }
    class CreditCardPayment {
        -cardNumber : String
        +process(amount) String
    }
    class PayPalPayment {
        -email : String
        +process(amount) String
    }
    class PaymentFactory {
        -paymentMethods : Map
        +createPayment(method, options) Payment
    }
    Payment <|-- CreditCardPayment
    Payment <|-- PayPalPayment
    PaymentFactory --> Payment
class Payment {
    process(amount) {
        throw new Error('process() 必須由子類別實作');
    }
}
 
class CreditCardPayment extends Payment {
    constructor({ cardNumber, cardHolder }) {
        super();
        this.cardNumber = cardNumber;
        this.cardHolder = cardHolder;
    }
 
    process(amount) {
        return `處理 ${this.cardHolder} 的信用卡支付 $${amount}`;
    }
}
 
class PayPalPayment extends Payment {
    constructor({ email }) {
        super();
        this.email = email;
    }
 
    process(amount) {
        return `處理 ${this.email} 的 PayPal 支付 $${amount}`;
    }
}
 
// 重點在這:工廠用 Map 管理,不用 if-else
class PaymentFactory {
    constructor() {
        this.paymentMethods = {
            CreditCard: CreditCardPayment,
            PayPal: PayPalPayment
        };
    }
 
    createPayment(method, options) {
        const PaymentMethod = this.paymentMethods[method];
        if (!PaymentMethod) throw new Error('未知的支付方式');
        return new PaymentMethod(options);
    }
 
    // 動態註冊——新增支付方式不用改工廠程式碼
    addPaymentMethod(name, PaymentClass) {
        this.paymentMethods[name] = PaymentClass;
    }
}

看到 addPaymentMethod 了嗎?這才是 Factory 最爽的地方——你可以在 runtime 動態註冊新的支付方式,完全不用改工廠本身的程式碼。

Factory 的三種口味

  1. Simple Factory:一個 method 搞定,適合種類少且穩定的情況
  2. Factory Method:把建立邏輯推給子類別,每個子類別決定自己要生什麼
  3. Abstract Factory:一次建立一整組相關物件(詳見 Abstract Factory 模式

大多數情況下,Simple Factory 就夠用了。別為了用 pattern 而用 pattern。

什麼時候不該用 Factory?

如果你的系統只有一兩種物件,而且未來也不太會新增——直接 new 就好。Factory 是為了管理「種類會成長」的場景設計的。硬要包一層工廠,只會讓同事覺得你在炫技。


Factory Pattern 就像是程式界的代工廠:你下單、它出貨、你不用管生產線長什麼樣子。但如果你只需要一支螺絲起子,就不用蓋一座工廠了


延伸閱讀