# 23. Template Method ###### tags: `DesignPatterns` ## 關於 Template Method 本篇將討論以下幾個問題 > ### 1. 關於 Template Method > ### 2. UML > ### 3. 將 UML 轉為程式碼 > ### 4. 情境 --- ## 測試環境: >OS:Windows 10 >IDE:Visual Studio 2019 --- ## 1. 關於 Template Method > Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure. > > by [Gang of Four](https://en.wikipedia.org/wiki/Design_Patterns) - 在操作中定義演算法的框架,將某些步驟延遲到子類別實作 - 樣板方法允許子類別在不更改演算法結構的情況下重新定義演算法的某些步驟 Template Method(樣板方法)屬於行為型(Behavioral Patterns),當遇到**大部分流程相同只有部分邏輯有差異** 時,可藉由 Template Method 將差異邏輯部分抽出各自實作,再依據需求選擇即可。 優點: - 提高程式碼複用性 缺點: - 框架限制修改幅度,修改幅度大則難以維護,修改幅度小則靈活性差 --- ## 2. UML ![](https://i.imgur.com/WjbAeZ9.jpg) Class 間關聯: - ConcreteClass 繼承 AbstractClass Class: - AbstractClass:定義演算法步驟的抽象類別 - ConcreteClass:實作演算法特定步驟 --- ## 3. 將 UML 轉為程式碼 定義演算法步驟的抽象類別 ```C# /// <summary> /// 定義演算法步驟的抽象類別 /// </summary> public abstract class AbstractClass { public abstract void PrimitiveOperation1(); public abstract void PrimitiveOperation2(); public void TemplateMethod() { PrimitiveOperation1(); PrimitiveOperation2(); Console.WriteLine(""); } } ``` 實作演算法特定步驟 A / B ```C# /// <summary> /// 實作演算法特定步驟 A /// </summary> public class ConcreteClassA : AbstractClass { public override void PrimitiveOperation1() { Console.WriteLine("ConcreteClassA.PrimitiveOperation1()"); } public override void PrimitiveOperation2() { Console.WriteLine("ConcreteClassA.PrimitiveOperation2()"); } } /// <summary> /// 實作演算法特定步驟 B /// </summary> public class ConcreteClassB : AbstractClass { public override void PrimitiveOperation1() { Console.WriteLine("ConcreteClassB.PrimitiveOperation1()"); } public override void PrimitiveOperation2() { Console.WriteLine("ConcreteClassB.PrimitiveOperation2()"); } } ``` 1. 分別建立 A / B 兩種實作 2. 執行 ```C# static void Main(string[] args) { Default.AbstractClass aA = new Default.ConcreteClassA(); aA.TemplateMethod(); Default.AbstractClass aB = new Default.ConcreteClassB(); aB.TemplateMethod(); Console.ReadLine(); } ``` 執行結果 ```Console ConcreteClassA.PrimitiveOperation1() ConcreteClassA.PrimitiveOperation2() ConcreteClassB.PrimitiveOperation1() ConcreteClassB.PrimitiveOperation2() ``` --- ## 4. 情境 我們接到了一依據資料時間選擇資料庫的需求 - 半年內的訂單存放在資料庫 A - 超過半年的訂單會存放在資料庫 B - 資料表的結構完全相同,只差在資料庫的位置不同 取得客戶訂單邏輯的抽象類別 ```C# /// <summary> /// 取得客戶訂單邏輯的抽象類別 /// </summary> public abstract class CustomerOrderInfo { public abstract void GetOrderInfo(); public void TemplateMethod() { GetOrderInfo(); Console.WriteLine(""); } } ``` 分別實作從資料庫 A / B 取得訂單資料 ```C# /// <summary> /// 從資料庫 A 取得訂單資訊 /// </summary> public class OrderInDBA : CustomerOrderInfo { public override void GetOrderInfo() { Console.WriteLine("從 DB A 取得半年內訂單資訊"); } } /// <summary> /// 從資料庫 B 取得訂單資訊 /// </summary> public class OrderInDBB : CustomerOrderInfo { public override void GetOrderInfo() { Console.WriteLine("從 DB B 取得超過半年內訂單資訊"); } } ``` 1. 建立`OrderInDBA`並取得半年內訂單資訊 2. 建立`OrderInDBB`並取得超過半年以上訂單資訊 ```C# static void Main(string[] args) { Situation.CustomerOrderInfo orderInSixMonth = new Situation.OrderInDBA(); orderInSixMonth.TemplateMethod(); Situation.CustomerOrderInfo orderOverSixMonth = new Situation.OrderInDBB(); orderOverSixMonth.TemplateMethod(); Console.ReadLine(); } ``` 執行結果 ```Console 從 DB A 取得半年內訂單資訊 從 DB B 取得超過半年以上訂單資訊 ``` --- ## 完整程式碼 GitHub:[Behavioral_10_TemplateMethod](https://github.com/darionnnnnn/blog/tree/master/Blog/Behavioral_10_TemplateMethod) --- ## 總結 ### 因為 Template Method 只開放有限度的接口,未來有新需求時,若是為了避免重構整個 Template Method 而在有限的接口中引用不屬於此接口的職責的物件,則會讓程式變得難以維護。 --- ## 參考資料 1. [Design Patterns](https://en.wikipedia.org/wiki/Design_Patterns) 2. [大話設計模式](https://www.tenlong.com.tw/products/9789866761799) 2. [dofactory](https://www.dofactory.com/) 3. [Refactoring.Guru](https://refactoring.guru/) --- ## 新手上路,若有錯誤還請告知,謝謝