Template Method Pattern(模板方法模式)

是什麼?

Template Method Pattern 在父類別中定義一個演算法的骨架(一系列步驟的順序),但把某些步驟的具體實作留給子類別。子類別可以重寫特定步驟,但不能改變整體流程。

ℹ️GoF 分類

Template Method 屬於行為型模式(Behavioral Pattern),重點在於用繼承來控制演算法流程,同時允許子類別自訂部分步驟。

什麼時候用?

什麼時候不該用?

⚠️過度設計警告

如果步驟變化太多,繼承層次會越來越深且難以維護。此時 Strategy Pattern(組合優於繼承)可能是更好的選擇。當流程本身也需要變化時,Template Method 會變得太僵硬。

執行流程

1

定義抽象類別

撰寫 Template Method,按順序呼叫各步驟

2

標記固定步驟

在父類別中實作不需要變化的步驟

3

標記抽象步驟

宣告為 abstract,強制子類別實作

4

提供 Hook 方法

可選步驟,子類別可以覆寫也可以不覆寫

5

子類別實作

繼承父類別,填入自訂步驟的具體邏輯

流程解讀:Template Method 把步驟分為三種:(1) 固定步驟 — 父類別直接實作,子類別不能覆寫(private 或 final)。(2) 抽象步驟 — 父類別宣告為 abstract,子類別必須實作。(3) Hook 方法(鉤子方法,指父類別提供的空實作方法,子類別可選擇性覆寫以插入自訂邏輯)— 父類別提供預設空實作,子類別可選擇性覆寫。Template Method 本身標記為 final/sealed,確保子類別不能改變流程順序。

程式碼範例

C# 版本

csharp
// 1. 抽象類別:定義模板
public abstract class DataMiner
{
    // Template Method — 定義流程骨架
    public void Mine(string path)
    {
        OpenFile(path);
        var rawData = ExtractData();
        var data = ParseData(rawData);
        AnalyzeData(data);
        SendReport();
        CloseFile();
    }
 
    // 固定步驟
    private void AnalyzeData(string data)
    {
        Console.WriteLine($"  分析資料:找到 {data.Length} 個字元");
    }
 
    private void SendReport()
    {
        Console.WriteLine("  報告已發送");
    }
 
    // 抽象步驟 — 子類別必須實作
    protected abstract void OpenFile(string path);
    protected abstract string ExtractData();
    protected abstract string ParseData(string rawData);
    protected abstract void CloseFile();
}
 
// 2. 具體實作:CSV 版本
public class CsvDataMiner : DataMiner
{
    protected override void OpenFile(string path)
        => Console.WriteLine($"  開啟 CSV 檔案:{path}");
    protected override string ExtractData()
        => "name,age,city";
    protected override string ParseData(string rawData)
        => rawData.Replace(",", " | ");
    protected override void CloseFile()
        => Console.WriteLine("  關閉 CSV 檔案");
}
 
// 3. 具體實作:JSON 版本
public class JsonDataMiner : DataMiner
{
    protected override void OpenFile(string path)
        => Console.WriteLine($"  開啟 JSON 檔案:{path}");
    protected override string ExtractData()
        => "{\"name\":\"Alice\",\"age\":30}";
    protected override string ParseData(string rawData)
        => rawData.Replace("{", "").Replace("}", "");
    protected override void CloseFile()
        => Console.WriteLine("  關閉 JSON 檔案");
}
 
// 4. 使用
DataMiner csvMiner = new CsvDataMiner();
csvMiner.Mine("data.csv");
 
DataMiner jsonMiner = new JsonDataMiner();
jsonMiner.Mine("data.json");

TypeScript 版本

typescript
// 1. 抽象類別
abstract class DataMiner {
  // Template Method
  mine(path: string): void {
    this.openFile(path);
    const rawData = this.extractData();
    const data = this.parseData(rawData);
    this.analyzeData(data);
    this.sendReport();
    this.closeFile();
  }
 
  private analyzeData(data: string): void {
    console.log(`  分析資料:找到 ${data.length} 個字元`);
  }
 
  private sendReport(): void {
    console.log("  報告已發送");
  }
 
  protected abstract openFile(path: string): void;
  protected abstract extractData(): string;
  protected abstract parseData(rawData: string): string;
  protected abstract closeFile(): void;
}
 
// 2. CSV 版本
class CsvDataMiner extends DataMiner {
  protected openFile(path: string) { console.log(`  開啟 CSV:${path}`); }
  protected extractData() { return "name,age,city"; }
  protected parseData(rawData: string) { return rawData.replace(/,/g, " | "); }
  protected closeFile() { console.log("  關閉 CSV 檔案"); }
}
 
// 3. JSON 版本
class JsonDataMiner extends DataMiner {
  protected openFile(path: string) { console.log(`  開啟 JSON:${path}`); }
  protected extractData() { return '{"name":"Alice","age":30}'; }
  protected parseData(rawData: string) { return rawData.replace(/[{}]/g, ""); }
  protected closeFile() { console.log("  關閉 JSON 檔案"); }
}
 
// 4. 使用
const csvMiner = new CsvDataMiner();
csvMiner.mine("data.csv");
 
const jsonMiner = new JsonDataMiner();
jsonMiner.mine("data.json");

Python 版本

python
from abc import ABC, abstractmethod
 
# 1. 抽象類別
class DataMiner(ABC):
    # Template Method
    def mine(self, path: str) -> None:
        self.open_file(path)
        raw_data = self.extract_data()
        data = self.parse_data(raw_data)
        self._analyze_data(data)
        self._send_report()
        self.close_file()
 
    def _analyze_data(self, data: str) -> None:
        print(f"  分析資料:找到 {len(data)} 個字元")
 
    def _send_report(self) -> None:
        print("  報告已發送")
 
    @abstractmethod
    def open_file(self, path: str) -> None:
        pass
    @abstractmethod
    def extract_data(self) -> str:
        pass
    @abstractmethod
    def parse_data(self, raw_data: str) -> str:
        pass
    @abstractmethod
    def close_file(self) -> None:
        pass
 
# 2. CSV 版本
class CsvDataMiner(DataMiner):
    def open_file(self, path: str) -> None:
        print(f"  開啟 CSV 檔案:{path}")
    def extract_data(self) -> str:
        return "name,age,city"
    def parse_data(self, raw_data: str) -> str:
        return raw_data.replace(",", " | ")
    def close_file(self) -> None:
        print("  關閉 CSV 檔案")
 
# 3. JSON 版本
class JsonDataMiner(DataMiner):
    def open_file(self, path: str) -> None:
        print(f"  開啟 JSON 檔案:{path}")
    def extract_data(self) -> str:
        return '{"name":"Alice","age":30}'
    def parse_data(self, raw_data: str) -> str:
        return raw_data.replace("{", "").replace("}", "")
    def close_file(self) -> None:
        print("  關閉 JSON 檔案")
 
# 4. 使用
csv_miner = CsvDataMiner()
csv_miner.mine("data.csv")
 
json_miner = JsonDataMiner()
json_miner.mine("data.json")

Java 版本

java
// 1. 抽象類別
public abstract class DataMiner {
    // Template Method
    public final void mine(String path) {
        openFile(path);
        String rawData = extractData();
        String data = parseData(rawData);
        analyzeData(data);
        sendReport();
        closeFile();
    }
 
    private void analyzeData(String data) {
        System.out.println("  分析資料:找到 " + data.length() + " 個字元");
    }
 
    private void sendReport() {
        System.out.println("  報告已發送");
    }
 
    protected abstract void openFile(String path);
    protected abstract String extractData();
    protected abstract String parseData(String rawData);
    protected abstract void closeFile();
}
 
// 2. CSV 版本
public class CsvDataMiner extends DataMiner {
    protected void openFile(String path) {
        System.out.println("  開啟 CSV 檔案:" + path);
    }
    protected String extractData() { return "name,age,city"; }
    protected String parseData(String rawData) {
        return rawData.replace(",", " | ");
    }
    protected void closeFile() { System.out.println("  關閉 CSV 檔案"); }
}
 
// 3. JSON 版本
public class JsonDataMiner extends DataMiner {
    protected void openFile(String path) {
        System.out.println("  開啟 JSON 檔案:" + path);
    }
    protected String extractData() { return "{\"name\":\"Alice\",\"age\":30}"; }
    protected String parseData(String rawData) {
        return rawData.replace("{", "").replace("}", "");
    }
    protected void closeFile() { System.out.println("  關閉 JSON 檔案"); }
}
 
// 4. 使用
DataMiner csvMiner = new CsvDataMiner();
csvMiner.mine("data.csv");
 
DataMiner jsonMiner = new JsonDataMiner();
jsonMiner.mine("data.json");

結構圖

Client
calls templateMethod()
AbstractClass (DataMiner)
ConcreteClass A (CsvMiner)
extends
ConcreteClass B (JsonMiner)

結構解讀:Client 呼叫 AbstractClass 的 Template Method,由父類別控制步驟順序。ConcreteClass A 和 B 各自覆寫特定步驟,但不能改變流程。這體現了「好萊塢原則(Hollywood Principle,即 Don't call us, we'll call you)」— 框架呼叫你的程式碼,而非你呼叫框架。

實戰補充

💡資深開發者經驗

框架到處都是 Template Method:ASP.NET 的 Controller 生命週期、React 的 Class Component(componentDidMountrender),都是 Template Method 的應用。

Hook 方法的威力:在 Template Method 中加入空的 Hook 方法(非抽象),讓子類別可以選擇性覆寫。例如 beforeExecute()afterExecute()

sealed/final 的重要性:Template Method 本身要標記為 sealed(C#)或 final(Java),防止子類別覆寫整個流程。這是「好萊塢原則(Don't call us, we'll call you)」的體現。

組合 vs 繼承:如果步驟變化太多,考慮用 Strategy Pattern 把每個步驟抽成獨立策略,用組合取代繼承,會更靈活。

理解測驗

🤔 Template Method Pattern 中,誰控制演算法的執行順序?

🤔 Template Method 和 Strategy Pattern 最大的差異是什麼?

🤔 為什麼 Template Method 要標記為 final 或 sealed?

面試常見問題

Q: Template Method 和 Strategy 都能自訂行為,怎麼選?

A: 核心差異是「繼承 vs 組合」和「控制範圍」。Template Method 用繼承,父類別控制整體流程,子類別只能自訂指定步驟。Strategy 用組合,Client 可以在執行時動態切換整個策略。選擇標準:如果流程骨架固定、只有個別步驟需要自訂 → Template Method;如果行為需要動態切換且不想被繼承綁死 → Strategy。

Q: Hook Method 是什麼?什麼時候用?

A: Hook Method 是 Template Method 中提供的「空方法」(有預設實作但什麼都不做),子類別可以選擇性覆寫。例如 beforeExecute()afterExecute()。用途是在不強制子類別實作的前提下提供擴展點。Abstract Method 是強制實作,Hook Method 是選擇性擴展。

相關模式

| 模式 | 關係 | |------|------| | Strategy | 都能實現可替換行為,但 Template Method 用繼承固定流程,Strategy 用組合動態替換。口訣:「Template 固定流程改步驟,Strategy 保持結構換策略」 | | Factory Method | Factory Method 常出現在 Template Method 內部,作為其中一個可覆寫的步驟 | | Hook Method | Template Method 中的可選擴展點,與 Abstract Method(必須實作)相對 |

重點整理

💡一句話記住

Template Method = 考卷模式:框架印好了,你只需填答案。 口訣:「父類定流程,子類填細節」

| 概念 | 說明 | |------|------| | Template Method | 父類別中定義的演算法骨架方法 | | Abstract Steps | 子類別必須實作的抽象步驟 | | Hook Methods | 子類別可選擇覆寫的空方法 | | 核心好處 | 消除重複的流程控制程式碼 | | 代價 | 繼承層次可能變深,靈活性不如組合 |

你可能也想看

Command PatternIterator Pattern

按 ← → 鍵切換課程