大家下載同一份「簡歷模板」(骨架:姓名、學歷、經歷、技能),但每個人填入的內容(細節)完全不同。模板方法模式正是將不變的流程固定在父類別,將變化的步驟交給子類別實作。
在抽象類別中定義演算法的骨架,將某些步驟延遲到子類別實作。模板方法讓子類別不改變演算法結構前提下,重新定義特定步驟。

「別呼叫我們,我們會呼叫你」 —— 父類別呼叫子類別方法,而非反向。
sealed(或非 virtual)防止被覆寫protected abstract 強制子類實作protected virtual 提供預設行為,子類選擇性覆寫
public abstract class SalaryCalculator
{
// === 模板方法:定義演算法骨架 ===
public sealed decimal CalculateNetSalary(Employee emp)
{
decimal basic = CalculateBasicSalary(emp); // 步驟 1:基本薪資
decimal bonus = CalculateBonus(emp); // 步驟 2:獎金
decimal allowance = CalculateAllowance(emp); // 步驟 3:津貼
decimal gross = basic + bonus + allowance; // 步驟 4:應發薪資
decimal tax = CalculateTax(gross, emp); // 步驟 5:扣稅
decimal insurance = CalculateInsurance(gross); // 步驟 6:保險
return gross - tax - insurance; // 步驟 7:實發薪資
}
// === 抽象基本步驟:子類別必須實作 ===
protected abstract decimal CalculateBasicSalary(Employee emp);
protected abstract decimal CalculateBonus(Employee emp);
// === 鉤子方法:提供預設實作,子類別可選擇覆寫 ===
protected virtual decimal CalculateAllowance(Employee emp) => 0m; // 預設無津貼
protected virtual decimal CalculateTax(decimal gross, Employee emp) => gross * 0.05m; // 預設 5%
protected virtual decimal CalculateInsurance(decimal gross) => gross * 0.02m; // 預設 2%
}
public class ManagerSalaryCalculator : SalaryCalculator
{
protected override decimal CalculateBasicSalary(Employee emp) => 80000m;
protected override decimal CalculateBonus(Employee emp)
=> emp.PerformanceRating switch
{
"A" => 30000m,
"B" => 20000m,
_ => 10000m
};
protected override decimal CalculateAllowance(Employee emp) => 10000m; // 職務津貼
}
public class CeoSalaryCalculator : SalaryCalculator
{
protected override decimal CalculateBasicSalary(Employee emp) => 200000m;
protected override decimal CalculateBonus(Employee emp) => emp.CompanyProfit * 0.01m; // 分紅
protected override decimal CalculateTax(decimal gross, Employee emp)
=> gross * 0.20m; // 高階主管稅率不同
protected override decimal CalculateInsurance(decimal gross)
=> 30000m; // 上限封頂
}
class Program
{
static void Main()
{
var empManager = new Employee { Id = 1, Name = "王經理", PerformanceRating = "A" };
var empCeo = new Employee { Id = 2, Name = "趙總", CompanyProfit = 50000000m };
var mgrCalc = new ManagerSalaryCalculator();
var ceoCalc = new CeoSalaryCalculator();
Console.WriteLine($"經理實發薪資: {mgrCalc.CalculateNetSalary(empManager):C}");
Console.WriteLine($"CEO 實發薪資: {ceoCalc.CalculateNetSalary(empCeo):C}");
Console.ReadKey();
}
}
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public string PerformanceRating { get; set; }
public decimal CompanyProfit { get; set; }
}
| 類別/方法 | 模板方法 | 可覆寫步驟 |
|---|---|---|
WebForms Page | ProcessRequest | OnInit, OnLoad, OnPreRender, Render |
ASP.NET Core Controller | ExecuteCoreAsync | OnActionExecuting, OnActionExecuted, OnResultExecuting |
Stream | Read, Write | ReadByte, WriteByte |
Entity Framework DbContext | SaveChanges | OnConfiguring, OnModelCreating, SaveChangesAsync |
| 優點 | 缺點 |
|---|---|
| ✅ 程式碼複用(共同流程集中) ✅ 反轉控制(父呼叫子) ✅ 符合開閉原則(擴充子類別不修改父類) ✅ 統一演算法結構,降低邏輯錯誤 |
⚠️ 類別數量增加(每個變體一個子類) ⚠️ 繼承耦合度較高 ⚠️ 步驟過多時模板方法過長,難以維護 |
模板方法模式是繼承複用的經典應用。關鍵在於:識別什麼是不變的(骨架),什麼是變化的(步驟)。善用 sealed 保護模板方法、abstract 強制實作、virtual 提供彈性,即可建立穩固且可擴充的演算法框架。