Template Method — 模板方法模式

1. 意圖

在抽象父類中定義演算法的骨架(final 方法),將某些步驟延遲到子類實作,讓子類在不改變整體結構的前提下覆寫特定步驟。

2. UML 類圖

classDiagram
    class AbstractClass {
        <>
        +templateMethod()
        #step1()*
        #step2()*
        #hook()
    }
    class ConcreteClassA {
        #step1()
        #step2()
    }
    class ConcreteClassB {
        #step1()
        #step2()
        #hook()
    }
    AbstractClass <|-- ConcreteClassA
    AbstractClass <|-- ConcreteClassB

3. 何時用 / 何時別用

用:

  • 多個類別共用相同的演算法骨架,只有部分步驟不同
  • 想控制子類能擴展的範圍(用 final 保護骨架,protected abstract 開放步驟)
  • 框架設計:讓使用者繼承後填入業務邏輯(Hollywood Principle)

別用:

  • 步驟差異大,繼承層次超過 2 層——改用 Strategy(組合優於繼承)
  • 子類只需客製 1 個步驟,傳入 callback / lambda 比繼承更輕量
  • 需要在執行期切換演算法變體,Template Method 是靜態綁定

4. Java 範例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// 資料匯出的通用骨架
public abstract class DataExporter {

    // 模板方法:final 保護流程
    public final void export(String destination) {
        List<String> data = fetchData();
        List<String> processed = transform(data);
        write(processed, destination);
        if (needsNotification()) {
            notify(destination);
        }
    }

    protected abstract List<String> fetchData();
    protected abstract List<String> transform(List<String> data);
    protected abstract void write(List<String> data, String destination);

    // hook:子類可選擇覆寫
    protected boolean needsNotification() { return false; }
    protected void notify(String destination) {
        System.out.println("Exported to: " + destination);
    }
}

// 具體實作 A:CSV 匯出
public class CsvExporter extends DataExporter {
    private final List<String> source;

    public CsvExporter(List<String> source) { this.source = source; }

    @Override
    protected List<String> fetchData() { return List.copyOf(source); }

    @Override
    protected List<String> transform(List<String> data) {
        return data.stream().map(s -> "\"" + s + "\"").toList();
    }

    @Override
    protected void write(List<String> data, String destination) {
        System.out.println("Writing CSV to " + destination + ": " + data);
    }

    @Override
    protected boolean needsNotification() { return true; } // 覆寫 hook
}

// 使用
DataExporter exporter = new CsvExporter(List.of("Alice", "Bob"));
exporter.export("/tmp/output.csv");
// Writing CSV to /tmp/output.csv: ["Alice", "Bob"]
// Exported to: /tmp/output.csv

5. JDK / Spring 真實案例

AbstractList:indexOf()、lastIndexOf()、iterator() 等方法的骨架實作在 AbstractList,只有 get(int index) 和 size() 是 abstract,ArrayList 與 LinkedList 各自提供這兩個方法。

HttpServlet:service() 方法依 HTTP method 呼叫 doGet()、doPost() 等,是 Servlet 規範的 Template Method,使用者繼承後只需覆寫對應方法。

Spring JdbcTemplate:execute(PreparedStatementCallback) 處理 Connection 取得、Statement 建立、例外轉換、Connection 釋放,業務邏輯透過回呼填入,是 Template Method 的回呼變體。

Spring AbstractBeanFactory.getBean():Bean 的建立流程(doGetBean)是模板,createBean() 是 abstract 步驟,由 AbstractAutowireCapableBeanFactory 實作。

6. 與相近 Pattern 的差異

維度Template MethodStrategy
機制繼承(靜態)組合(動態)
骨架固定在父類Context 決定呼叫哪個 Strategy
切換編譯期決定(子類)執行期決定(注入不同 Strategy)
耦合子類與父類強耦合Context 與 Strategy 弱耦合

當繼承層次超過 2 層或步驟在執行期需要切換,改用 Strategy。

7. 現代 Java 取代方案

可用函數式回呼取代繼承,避免類別爆炸:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 將可變步驟提取為 Function 參數
public class DataExporter {

    public void export(
            Supplier<List<String>>        fetcher,
            UnaryOperator<List<String>>   transformer,
            BiConsumer<List<String>, String> writer,
            String destination) {

        List<String> data = fetcher.get();
        List<String> processed = transformer.apply(data);
        writer.accept(processed, destination);
    }
}

// 呼叫端用 lambda 組裝,無需繼承
DataExporter exporter = new DataExporter();
exporter.export(
    () -> List.of("Alice", "Bob"),
    data -> data.stream().map(String::toUpperCase).toList(),
    (data, dest) -> System.out.println("Write to " + dest + ": " + data),
    "/tmp/output.txt"
);

步驟多、流程複雜且需要 hook 控制分支時,傳統 Template Method 的可讀性優於一大串 lambda。

8. 參考資料

  • GoF Design Patterns (1994), ch.5 Template Method
  • Effective Java 3rd ed., Item 20 — Prefer interfaces to abstract classes
  • Spring Framework Source — JdbcTemplate, AbstractBeanFactory

留言討論