在開發過程中,我們常遇到一類問題:演算法的整體流程固定,但某些步驟的具體實作因子系統而異。例如「先驗證權限,再查詢資料」,驗證邏輯固定,查詢來源可能是 SQL Server、XML 或 API。若在每個子類別重複整個流程,違反 DRY 原則;若將流程寫在基類別,又難以客製化步驟。模板方法模式正是為了解決此類問題而生:在基類別定義演算法骨架,將易變步驟延遲至子類別實作。
本範例以訊息查詢為例,展示「先驗證用戶,再取得資料」的固定流程:
AbstractMessageTemplate,定義模板方法 TemplateMethodGet() 及基本步驟 ValidateUser()(可覆寫)、Get()(抽象,強制子類別實作)。SqlMessage、XmlMessage,實作 Get() 提供不同資料來源查詢邏輯。
AbstractMessageTemplate 封裝不變的流程:
public List<MessageModel> TemplateMethodGet()
{
if (ValidateUser())
{
return Get(); // 延遲至子類別
}
else
{
List<MessageModel> l = new List<MessageModel>();
l.Add(new MessageModel("無權獲取", DateTime.Now));
return l;
}
}
// 可被子類別覆寫的鉤子方法
public virtual bool ValidateUser()
{
return this._userId == "admin";
}
public abstract List<MessageModel> Get(); // 必須實作
關鍵設計點:
TemplateMethodGet() 為 final(C# 中非 virtual 即不可覆寫),保護演算法結構不被破壞。ValidateUser() 宣告為 virtual,提供預設實作,子類別可選擇性覆寫(Hook 方法)。Get() 為 abstract,強制子類別實作核心差異邏輯。SqlMessage 與 XmlMessage 僅需實作 Get():
public override List<MessageModel> Get()
{
List<MessageModel> l = new List<MessageModel>();
l.Add(new MessageModel("SQL方式獲取Message", DateTime.Now));
return l;
}
構造函數透過 base(userId) 傳遞用戶識別碼給基類別,確保驗證邏輯可用。
AbstractMessageTemplate m = new XmlMessage("user"); // 非 admin
Response.Write(m.TemplateMethodGet()[0].Message); // 輸出:無權獲取
m = new SqlMessage("admin"); // admin
Response.Write(m.TemplateMethodGet()[0].Message); // 輸出:SQL方式獲取Message
無權獲取 2007-3-13 22:29:55
SQL方式獲取Message 2007-3-13 22:29:55
結果驗證:相同流程(驗證→查詢),不同用戶權限與資料來源產生不同結果,且核心流程程式碼零重複。
模板方法模式體現「好萊塢原則」——「別呼叫我們,我們會呼叫你」。基類別掌控流程,子類別只在特定點被「回呼」。進階技巧包含:
ValidateUser() 提供預設行為,子類別可選擇覆寫或保留。abstract 強制實作核心步驟;virtual 提供預設實作作為可選擴展點。sealed(或非 virtual),確保演算法骨架不被子類別竄改。模板方法模式是繼承體系中「複用與擴展」平衡的經典實踐。本範例以權限驗證+資料查詢為例,展示如何將不變流程封裝於基類別 TemplateMethodGet(),將變化查詢來源延遲至 SqlMessage、XmlMessage 實作。掌握此模式,能讓你在設計框架、庫或業務流程時,建立出「結構穩固、擴展靈活」的程式碼架構,有效應對「流程固定、細節多變」的業務挑戰。