繼承 vs. 委派#
1990 年代初期,OO 社群對繼承(inheritance)非常著迷——透過繼承可以「因差異而編程」(program by difference),只需建立子類別就能重用程式碼。然而到了 1995 年,GoF 提出了著名的建議:「優先使用物件組合而非類別繼承」(Favor object composition over class inheritance)。
本章探討的兩個模式正好體現了繼承與委派(delegation)的差異:
- Template Method 使用繼承來解決問題
- Strategy 使用委派來解決問題
- 兩者都在解決同一個問題:將通用演算法與詳細實作分離,符合 DIP(依賴反轉原則)
Template Method 模式#
基本結構#
幾乎所有程式都有這個基本的 main loop 結構:
Initialize();
while (!Done()) // main loop
{
Idle(); // do something useful.
}
Cleanup();Template Method 模式將這個通用結構擷取到抽象基底類別的已實作方法中,將所有細節延遲到抽象方法:
public abstract class Application
{
private bool isDone = false;
protected abstract void Init();
protected abstract void Idle();
protected abstract void Cleanup();
public void Run()
{
Init();
while (!Done())
Idle();
Cleanup();
}
}子類別只需繼承 Application 並填入抽象方法的實作即可。
Pattern Abuse(模式濫用)#
注意: 作者承認用 Template Method 來封裝
ftoc這種簡單程式的 main loop 是模式濫用的好例子。設計模式的存在不代表它們應該被到處使用——在這個案例中,Template Method 的成本高於它帶來的效益。
Bubble Sort 範例#
更實用的 Template Method 範例是 Bubble Sort。將排序演算法抽取到抽象基底類別,把比較(OutOfOrder)和交換(Swap)延遲為抽象方法:
public abstract class BubbleSorter
{
protected int DoSort()
{
// ... 排序演算法 ...
for (...)
for (...)
{
if (OutOfOrder(index))
Swap(index);
}
}
protected abstract void Swap(int index);
protected abstract bool OutOfOrder(int index);
}衍生類別如 IntBubbleSorter 和 DoubleBubbleSorter 分別處理不同型別的陣列:

Figure 22.1: Bubble sorter structure
重點: Template Method 展示了物件導向程式設計中經典的重用形式——通用演算法放在基底類別,被繼承到不同的詳細情境中。但繼承是非常強的關係,衍生類別與基底類別密不可分。例如
IntBubbleSorter的OutOfOrder和Swap方法完全可以用於其他排序演算法,但因為繼承了BubbleSorter,它們被永遠綁在一起。
Strategy 模式#
基本結構#
Strategy 模式用完全不同的方式反轉通用演算法與詳細實作的依賴關係。不是把通用演算法放在抽象基底類別,而是放在一個具體類別中;抽象方法被定義在一個介面中,具體類別透過委派呼叫介面:

Figure 22.2: STRATEGY structure of the Application algorithm
public class ApplicationRunner
{
private Application itsApplication = null;
public ApplicationRunner(Application app)
{
itsApplication = app;
}
public void run()
{
itsApplication.Init();
while (!itsApplication.Done())
itsApplication.Idle();
itsApplication.Cleanup();
}
}
public interface Application
{
void Init();
void Idle();
void Cleanup();
bool Done();
}Bubble Sort 的 Strategy 版本#
在 Strategy 版本中,BubbleSorter 變成具體類別,透過 SortHandler 介面委派比較和交換操作:
public interface SortHandler
{
void Swap(int index);
bool OutOfOrder(int index);
int Length();
void SetArray(object array);
}IntSortHandler 實作 SortHandler 介面,但完全不知道 BubbleSorter 的存在。這意味著 IntSortHandler 可以被用於任何其他排序演算法(如 QuickBubbleSorter),而不僅僅是 BubbleSorter。
技巧: Template Method 部分違反了 DIP——
Swap和OutOfOrder的實作直接依賴排序演算法。Strategy 則沒有這種依賴。因此 Strategy 額外提供了一個好處:每個詳細實作可以被多個不同的通用演算法操縱。
Template Method vs. Strategy 比較#
flowchart LR
subgraph tm ["Template Method"]
A1["抽象基底類別<br/>通用演算法"] -->|繼承| B1["子類別 A<br/>具體實作"]
A1 -->|繼承| C1["子類別 B<br/>具體實作"]
end
subgraph st ["Strategy"]
A2["具體類別<br/>通用演算法"] -->|委派| I2[介面]
I2 -->|實作| B2[策略 A]
I2 -->|實作| C2[策略 B]
end| 面向 | Template Method | Strategy |
|---|---|---|
| 機制 | 繼承 | 委派(組合) |
| 演算法位置 | 抽象基底類別的已實作方法 | 具體類別 |
| 詳細實作 | 子類別覆寫抽象方法 | 實作介面的獨立類別 |
| 耦合度 | 衍生類別與基底類別緊密耦合 | 實作類別與演算法類別完全解耦 |
| DIP 符合度 | 部分違反(衍生類別依賴基底類別) | 完全符合 |
| 靈活度 | 較低(實作綁定到特定演算法) | 較高(實作可被多個演算法重用) |
| 複雜度 | 較低(少一個類別) | 較高(多一個類別和委派間接層) |
結論#
- Template Method 寫起來簡單、用起來簡單,但缺乏彈性
- Strategy 具有彈性,但需要多建立一個類別、多一個物件、多一層佈線
- 選擇取決於是否需要 Strategy 的靈活性,或者能接受 Template Method 的簡單性
- 作者表示自己多數時候選擇 Template Method,因為它更容易實作和使用。除非確定需要不同的排序演算法,否則不必使用 Strategy