設計模式:工廠方法 (Factory Method)

10 分鐘
軟體設計設計模式OOP

工廠方法 (Factory Method) 是創建型模式,用來解決一個問題:一個父類別知道自己需要建立物件,卻不知道該建立哪個具體型別。

它把建立物件這件事定義成一個介面 (也就是工廠方法),再交由子類別決定要實例化哪個具體類別。


意圖與用途

想像一個發送通知的系統,具體的通知方式可能是 Email、SMS 或 Push,要走哪個管道則取決於使用者設定或環境變數。

工廠方法讓我們可以:

  • 把各種通知的建立邏輯,封裝在各自的子類別裡
  • 新增通知管道時,不必修改既有程式碼
  • 讓呼叫端只跟抽象的 Notification 介面打交道

結構與角色

Factory Method 有四個重要角色:

  • Product:被建立物件的抽象介面 (Notification)
  • ConcreteProduct:具體的產品實作 (EmailNotificationSMSNotification)
  • Creator:宣告工廠方法的抽象類別 (NotificationSender)
  • ConcreteCreator:實作工廠方法的子類別 (EmailSenderSMSSender)

實作範例:通知系統

TypeScript
// Product 介面
interface Notification {
  send(message: string): void;
}

// ConcreteProduct 實作
class EmailNotification implements Notification {
  constructor(private to: string) {}

  send(message: string): void {
    console.log(`Sending email to ${this.to}: ${message}`);
  }
}

class SMSNotification implements Notification {
  constructor(private phone: string) {}

  send(message: string): void {
    console.log(`Sending SMS to ${this.phone}: ${message}`);
  }
}

class PushNotification implements Notification {
  constructor(private deviceToken: string) {}

  send(message: string): void {
    console.log(`Sending push to ${this.deviceToken}: ${message}`);
  }
}

// Creator 抽象類 —— 義工廠方法
abstract class NotificationSender {
  // 工廠方法:由子類別實作
  abstract createNotification(target: string): Notification;

  // 共用的業務邏輯,會用到工廠方法
  notify(target: string, message: string): void {
    const notification = this.createNotification(target);
    notification.send(message);
  }
}

// ConcreteCreator
class EmailSender extends NotificationSender {
  createNotification(email: string): Notification {
    return new EmailNotification(email);
  }
}

class SMSSender extends NotificationSender {
  createNotification(phone: string): Notification {
    return new SMSNotification(phone);
  }
}

class PushSender extends NotificationSender {
  createNotification(deviceToken: string): Notification {
    return new PushNotification(deviceToken);
  }
}

// 使用
const sender: NotificationSender = new EmailSender();
sender.notify('user@example.com', '你的訂單已出貨');

// 新增 LINE 通知?只需新增類別,其他程式碼都不用動
class LineNotification implements Notification {
  constructor(private userId: string) {}
  send(message: string): void {
    console.log(`Sending LINE to ${this.userId}: ${message}`);
  }
}

class LineSender extends NotificationSender {
  createNotification(userId: string): Notification {
    return new LineNotification(userId);
  }
}

新增 LINE 通知時,NotificationSenderEmailSenderSMSSender 完全不需要動。


適用情境

適用時機

  • 要建立的具體型別得交由子類別決定
  • Creator 裡有共用的業務邏輯,而這段邏輯會用到工廠方法
  • 預期未來會新增更多型別,希望擴充時只是「加東西」而不是改舊程式碼

與簡單 Factory Function 的差別

如果只是想把建立邏輯集中起來,一個單純的 factory function 通常就夠了:

TypeScript
function createNotification(type: 'email' | 'sms', target: string): Notification {
  if (type === 'email') return new EmailNotification(target);
  return new SMSNotification(target);
}

Factory Method 的價值,在於它結合了繼承帶來的共用邏 —— Creator 除了工廠方法之外還有共用的業務邏輯、整個樣板方法 (Template Method) 結構已經就位時,就是它派上用場的時機。


總結

Factory Method 的核心就是「將建立物件的職責延遲到子類別」。

這麼做的好處是,父類別不必知道具體型別就能操作物件,而子類別則掌控具體的建立方式。新增型別時,只要新增一個子類 —— 不必去改一條越長越長的 if-else,也不必動到既有的類別。