設計模式:樣板方法模式 (Template Method)

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

Template Method 在基底類別定義一個演算法的骨架,子類別則覆寫其中特定的步驟來客製行為,而不動到整體流程。


意圖與用途

想像一個報表系統,每一種報表都會走過相同的步驟:取得資料 → 處理資料 → 渲染輸出。但每個步驟的實際內容,會隨報表類型而不同。

如果不加抽象,每個報表類別都會重複寫出同一套流程骨架。Template Method 把流程鎖在基底類別裡,只讓子類別實作那些會變動的部分。


結構與角色

  • AbstractClass:定義樣板方法 (流程骨架) 以及抽象步驟
  • templateMethod():依序呼叫各步驟,本身不應被覆寫
  • abstractStep():留給子類別實作的掛勾
  • ConcreteClass:提供各步驟的具體實作

實作範例:資料報表產生流程

TypeScript
abstract class ReportGenerator {
  // 樣板方法:固定的流程
  generate(): string {
    const data = this.fetchData();
    const processed = this.processData(data);
    const header = this.renderHeader();
    const body = this.renderBody(processed);
    const footer = this.renderFooter();
    return [header, body, footer].join('\n');
  }

  // 抽象步驟:由子類別實作
  protected abstract fetchData(): Record[];
  protected abstract renderBody(data: Record[]): string;

  // 預設實作,子類別可覆寫
  protected processData(data: Record[]): Record[] {
    return data;
  }

  protected renderHeader(): string {
    return '=== Report ===';
  }

  protected renderFooter(): string {
    return `Generated at ${new Date().toISOString()}`;
  }
}

// 銷售報表
class SalesReport extends ReportGenerator {
  protected fetchData(): Record[] {
    // 實際實作會從 API 或資料庫取得資料
    return [
      { product: 1, revenue: 12000 },
      { product: 2, revenue: 8500 },
    ];
  }

  protected processData(data: Record[]): Record[] {
    // 額外處理:依營收由高到低排序
    return [...data].sort((a, b) => b['revenue'] - a['revenue']);
  }

  protected renderBody(data: Record[]): string {
    return data.map(row => `Product ${row['product']}: $${row['revenue']}`).join('\n');
  }

  protected renderHeader(): string {
    return '=== Sales Report ===';
  }
}

// 庫存報表
class InventoryReport extends ReportGenerator {
  protected fetchData(): Record[] {
    return [
      { sku: 1001, stock: 45 },
      { sku: 1002, stock: 3 },
    ];
  }

  protected renderBody(data: Record[]): string {
    return data
      .map(row => {
        const warning = row['stock'] < 10 ? ' [低庫存警告]' : '';
        return `SKU ${row['sku']}: ${row['stock']} 件${warning}`;
      })
      .join('\n');
  }
}

// 使用
const salesReport = new SalesReport();
console.log(salesReport.generate());

const inventoryReport = new InventoryReport();
console.log(inventoryReport.generate());

適用情境

適用時機

  • 多個類別共用同一套執行流程,但個別步驟各有不同
  • 想固定住整體結構不讓子類別改動,只開放特定步驟讓它們客製

Template Method vs. Strategy

Template MethodStrategy
流程骨架固定在基底類別完全可替換
實作機制繼承組合
切換時機編譯期執行期

總結

Template Method 是類別層級的控制反轉:是基底類別去呼叫子類別,而不是反過來。

當多個類別共用同一套流程、只在少數幾個步驟上有差異時,Template Method 能乾淨俐落地消除這份重複。