--- tags: 設計模式 --- # 範本模式(Template Method Pattern) ## 前言 - 當你只想改變或擴充演算法的某幾個步驟時 , 可以考慮用 Template Method Pattern - 當你有複數個類別有幾乎一樣的演算法時 (重複的程式碼), 可以考慮用 Template Method Pattern 抽取相同的演算法到父類別, 而不同的部分則留給子類別擴充. #### 問題需求 ![20190614-拿鐵卡布奇諾瑪琪雅朵-封面圖.jpg](https://github.com/s0920832252/C_Sharp/blob/master/Files/DesignPattern/20190614-拿鐵卡布奇諾瑪琪雅朵-封面圖.jpg?raw=true) - 有一家咖啡店希望請你撰寫自動泡咖啡的程式. 所以你先定義了一個咖啡類別, 此類別負責控制機器沖泡咖啡. 但有一個令你很苦惱的事情是咖啡店提供的咖啡不只一種品牌, 而每種咖啡品牌在一些步驟上有若干微小的差異. 像是水/牛奶/咖啡的比例不一樣等等. ```C# // First Version public abstract class 咖啡 { public void 煮開水(){ // 煮開水 }; public abstract void 泡咖啡(); } public class 卡布奇諾 : 咖啡 { public override void 泡咖啡() { 煮開水(); 加水到杯子中(); 放牛奶到杯子中(); 放咖啡豆去弄成粉並加入到杯子中(); } private void 加水到杯子中() { // 加入 20 % 的水 } private void 放牛奶到杯子中() { // 加入 70 % 的牛奶 } private void 放咖啡豆去弄成粉並加入到杯子中() { // 加入 10 % 的咖啡 } } public class 拿鐵 : 咖啡 { public override void 泡咖啡() { 煮開水(); 加水到杯子中(); 放牛奶到杯子中(); 放咖啡豆去弄成粉並加入到杯子中(); } private void 加水到杯子中() { // 加入 10 % 的水 } private void 放牛奶到杯子中() { // 加入 85 % 的牛奶 } private void 放咖啡豆去弄成粉並加入到杯子中() { // 加入 5 % 的咖啡 } } // 還有其他咖啡類別... ``` - 泡咖啡()的實作是重複的. 只要有一個新的咖啡品牌的類別就必須把 泡咖啡() 的程式碼複製貼上過去... ---> 子類別重複的程式碼產生 ---> 這是壞味道 - 泡咖啡的整體步驟是一樣的, 但拿鐵的某個步驟細節可能跟卡布奇諾不同. ## Template Method 介紹 ### 定義 > Template Method is a behavioral design pattern that defines the skeleton of an algorithm in the superclass but lets subclasses override specific steps of the algorithm without changing its structure. > 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 alorithm's structure. > * Applicability > * Need to implement the invariant parts of an algorithm, but leave some details to subclasses > * Need to factor out common behavior in groups of subclasses > * Need to control subclass extensions > * Consequences > * Fundamental technique for code reuse > * Often called hooks > * Template methods may be optional or required - 在一個方法中定義演算法框架/流程/骨幹/結構. 而其中一些步驟(方法/實作細節) 將會遞延到子類別才實作. 此使得子類別可以在不改變原有演算法的框架/流程/骨幹/結構下, 即可重新定義該演算法的某些特定步驟. - Template Method 需要在父類別的一個方法中定義一個演算法的每一個步驟. - 每一個步驟都可以是一個方法的呼叫, 只是這些方法可能會遞延到子類別才實作. (可能會, 但也可以不用) - 步驟是否在父類別實作, 取決於程式執行時當下的環境是否允許.(是否要留彈性) - 定義步驟時, 可考慮在父類別提供預設的處理實作. 但保留日後在子類別被複寫的可能性.(若未來需要透過 override 重新定義實作細節) - Template Method 在基底類別實作共用功能(重複的程式碼), 所以 Template Method 可以減少重複的程式碼 ### 名詞定義 - AbstractClass - 定義演算法主架構的類別, 以抽象類別(abstract class)的形式呈現. - 在某個方法(Template Method) 中定義完整的演算法(程式執行的流程) - Template Method 定義演算法的輪廓(方法執行的順序). 並且會呼叫 Primitive Operation(s) 以及其他方法(e.g. 父類別私有方法抑或是其他物件所提供的方法)以完成功能. - 演算法中的部分步驟(Primitive Operation)不會在父類別實作, 將會遞延到子類別實作. - 完整演算法中某些會變化的步驟會被定義為 Primitive Operation, 通常為 abstract 成員, 但也可以定義為 virtual 成員. - Primitive Operation 的實作細節會由子類別來實作 - ConcreteClass - 繼承 AbstractClass 的子類別, 需要實作必要的 Primitive Operation. ### UML Illustrate ![structure-Template.png](https://github.com/s0920832252/C_Sharp/blob/master/Files/DesignPattern/structure-Template.png?raw=true) ```sequence Client->ConcreteClass:TemplateMethod() ConcreteClass->ConcreteClass:PrimitiveOperation1() ConcreteClass->ConcreteClass:PrimitiveOperation2() ConcreteClass->Client:Return or End ``` ### Pseudo Code ```C# public abstract class AbstractClass { public void TemplateMethod() { PrimitiveOperation1(); PrimitiveOperation2(); } protected abstract void PrimitiveOperation1(); protected abstract void PrimitiveOperation2(); } public class ConcreteClass1 : AbstractClass { protected override void PrimitiveOperation1() => throw new NotImplementedException(); protected override void PrimitiveOperation2() => throw new NotImplementedException(); } // 以此類推... ~ ConcreteClassN ``` ## Code Example ### 咖啡 example (使用範本模式) ```C# public abstract class 咖啡 { public void 泡咖啡() { 煮開水(); 加水到杯子中(); 放牛奶到杯子中(); 放咖啡豆去弄成粉並加入到杯子中(); } public void 煮開水() { // 煮開水 } protected abstract void 加水到杯子中(); protected abstract void 放牛奶到杯子中(); protected abstract void 放咖啡豆去弄成粉並加入到杯子中(); } public class 卡布奇諾 : 咖啡 { protected override void 加水到杯子中() { // 加入 20 % 的水 } protected override void 放牛奶到杯子中() { // 加入 70 % 的牛奶 } protected override void 放咖啡豆去弄成粉並加入到杯子中() { // 加入 10 % 的咖啡 } } public class 拿鐵 : 咖啡 { protected override void 加水到杯子中() { // 加入 10 % 的水 } protected override void 放牛奶到杯子中() { // 加入 85 % 的牛奶 } protected override void 放咖啡豆去弄成粉並加入到杯子中() { // 加入 5 % 的咖啡 } } ``` ### Template Method & Factory ##### 實務上有可能將各種模式混在一起使用, 此 Example 為 Template Method & Factory 混合 ```C# public abstract class Component { public void Operation() => throw new NotImplementedException(); } public class ComponentForMac : Component {} public class ComponentForWindows : Component {} public abstract class Compute { public void Run() // Template Method { var component = CreateComponent(); // Call Primitive Operation component.Operation(); } public abstract Component CreateComponent(); // CreateProduct : abstractProduce } public class Mac : Compute { public override Component CreateComponent() => new ComponentForMac(); } public class Windows : Compute { public override Component CreateComponent() => new ComponentForWindows(); } ``` - 專注在 Compute 的 CreateComponent 以及在子類才實作 CreateComponent 的細節這個點. ---> 由子類決定物件建立, 這是 Factory Pattern. - 專注在 Compute 的 Run(), 在 Run 內會呼叫 CreateComponent (Primitive Operation), 以及子類才實作 CreateComponent(Primitive Operation) 這個點. ---> 由子類決定行為的實作細節, 這是 Template Method Pattern. - [Is Factory method pattern a specialized case of Template method pattern?](https://stackoverflow.com/a/62627533/11501584) > Both Factory method pattern and Template method pattern have similar design but they vary for their purpose > > Factory method is a creational pattern where object creation is the responsibility of the child class. > > A pattern where a class defines an abstract method for an object creation and another method using the created object and thereby allowing the subclasses to provide the implementation of the creational method is factory method. > > Template method is a behavioral pattern where behaviour is the responsibility of the sub class. > > A pattern where a class defines an abstract method for a behaviour and another method invokes the abstract method to have the behaviour executed, the behaviour is implemented by the subclass. Hence its the parent class invoking the implementation of the child class without having any explicit compile time dependency on its subclass. This is true for the factory method pattern as well. But both differs in their intent. > > In other words we can say Factory method pattern creates object in a similar was as Template method pattern executes the behaviour. ### 比較 Template Method VS Strategy VS Delegate 的差異 - #### 使用 Template Method 實作 LinQ Select ##### AbstractClass ```C# public abstract class MySelector<TSource,TTarget> { private readonly IEnumerable<TSource> _collection; protected MySelector(IEnumerable<TSource> collection) => _collection = collection; public IEnumerable<TTarget> SelectOperation() // Template Method { foreach (var element in _collection) { yield return Transform(element); } } protected abstract TTarget Transform(TSource element); // PrimitiveOperation } ``` ##### ConcreteClass ```C# public class TransformSquareTimes : MySelector<double, double> { public TransformSquareTimes(IEnumerable<double> collection) : base(collection){} protected override double Transform(double element) => element * element; } // 或許還有 TransformSquareTimesN , 以此類推... ``` ##### Client ```C# internal static class Program { private static void Main(string[] args) { var list = new List<double>(){1,3,5,7,9}; var squareTimes = new TransformSquareTimes(list); foreach (var num in squareTimes.SelectOperation()) { Console.WriteLine(num); } Console.ReadKey(); } } ``` ##### 輸出 ``` 1 9 25 49 81 ``` - 使用 Strategy 實作 LinQ Select ##### AbstractStrategy ```C# public interface IMySelector<in TSource, out TTarget> { TTarget SelectOperation(TSource element); } ``` ##### ConcreteStrategy ```C# public class TransformSquareTimesStrategy : IMySelector<double, double> { public double SelectOperation(double element) => element * element; } // 或許還有 TransformSquareTimesStrategyN , 以此類推... ``` ##### StrategyContext ```C# public static class SelectStrategyContext { public static IEnumerable<TTarget> SelectOperation<TSource, TTarget>( this IEnumerable<TSource> collection, IMySelector<TSource, TTarget> selector) // 策略是透過參數傳入. { foreach (var item in collection) { yield return selector.SelectOperation(item); } } } ``` ##### Client ```C# internal static class Program { private static void Main(string[] args) { var list = new List<double> { 1, 3, 5, 7, 9, }; var strategy = new TransformSquareTimesStrategy(); foreach (var num in list.SelectOperation(strategy)) { Console.WriteLine(num); } Console.ReadKey(); } } ``` ##### 輸出 ``` 1 9 25 49 81 ``` - 使用 Delegate 實作 LinQ Select ##### Enumerable ```C# public static class MyEnumerableForSelect { public static IEnumerable<TTarget> SelectOperation<TSource, TTarget>( this IEnumerable<TSource> collection, Func<TSource, TTarget> selector) { foreach (var item in collection) { yield return selector(item); } } } ``` ##### Client ```C# internal static class Program { private static void Main(string[] args) { var list = new List<double> { 1, 3, 5, 7, 9, }; Func<double, double> func = element => element * element; foreach (var num in list.SelectOperation(func)) { Console.WriteLine(num); } Console.ReadKey(); } } ``` ##### 輸出 ``` 1 9 25 49 81 ``` - 這三個 Code Example 證明了 Template Method Pattern 和 Strategy Pattern 以及 Delegate 的實作是可以互相替換的. - Template Method Pattern 透過子類別 override 的方式修改 Primitive Operation(行為). 其在變化是靜態的, 當類別型別決定時, 變化就決定了. - 策略模式是透過從外部傳入策略物件的方式決定變化. 其變化是動態的, 其變化取決於傳入的參數策略物件為何. - 自由度由小到大排序是 **Template Method Pattern** < **Strategy Pattern** < **Delegate** - 使用哪種方式取決於你願意給 Client 多大的自由度(好用). - 給 Client 越大的自由, Debug 的機率越高 (笑) - 你永遠不知道自由奔放的 Client 會做出什麼操作, 導致你的程式掛掉... ## 結論 - Template Method Pattern 遵守開放封閉原則 Open-Closed Principle (OCP) - 當演算法的每一個步驟都**需要很大的變化時**, 可以考慮將 Template Method Pattern 替換成 Strategy or Delegate 的方式實作. - 使用 Template Method Pattern 時, 需要遵守 Narrow Inheritance Interface Principle - AbstractClass 內定義的 PrimitiveOperation(**必須 Override 的**) 不應該太多, 否則可能會導致 ConcreteClass 需要 override 其不需要 override 的PrimitiveOperation - 可考慮適時讓 AbstractClass 實作 PrimitiveOperation (但使用 virtual 關鍵字, 保留讓 ConcreteClass override 的自由) - 當類別的內聚性較低的時候(Template Method 與類別內成員的關聯性不大) , 可以考慮將 Template Method Pattern 替換成 Strategy or Delegate 的方式實作. - Template Method Pattern 通常使用的情境是演算法足夠複雜, 但又**需要在子類別才能調整部分細節** (透過 override Primitive Operation ). - **範本模式透過把不變的行為抽移到父類別, 以去除子類別中的重複程式碼.** - 有時候當不會變化和會變化的行為同時出現在子類別中時, 不會變化的行為就會在子類別中重複出現時. 此時可考慮透過 Template Method 幫助子類別減少重複程式碼的壞味道. - Template Method Pattern 透過 override 演算法中的某一個步驟(Primitive Operation) , 從而減少對演算法其他部分發生更改的影響. - **Template Method Pattern 的核心是使用繼承的技巧 --- 透過在子類別中 override 特定步驟(Primitive Operation) 來改變演算法的某個細節.** Strategy Pattern 的核心是使用組合的技巧 --- 透過提供相對應的策略物件(StrategyContext)來改變對象的部分行為. - Template Method Pattern 透過子類別 override 的方式修改 Primitive Operation(行為). 其在變化是靜態的, 當類別型別決定時, 變化就決定了. - 策略模式是透過從外部傳入策略物件的方式決定變化. 其變化是動態的, 其變化取決於傳入的參數策略物件為何. - Client 在使用上, 可能會受到所提供的演算法框架的限制. - Template Method Pattern 必須使用到繼承的技巧, 這增加違反里式替換原則 Liskov’s Substitution Principle (LSP) 的風險 ```C# // e.g. 父類別(抽象)定義了預設的方法 public abstract class 鳥 { public void 飛行() => 起飛(); protected void 起飛() => Console.WriteLine("起飛"); } public class 企鵝 : 鳥 { protected new void 起飛() => throw new NotImplementedException(); } ``` - Factory Pattern 是 Template Method Pattern 的一個特殊形式. Factory Pattern 的生產方法可當作為 Template Method Pattern 中的一個步驟. ## 參考 [Template Method](https://refactoring.guru/design-patterns/template-method) [TEMPLATE METHOD](https://openhome.cc/zh-tw/pattern/behavioral/template-method/) [Template Method Behavioral pattern](https://reactiveprogramming.io/blog/en/design-patterns/template-method) [C# Design Pattern - Template Method Pattern 範本方法模式](http://e-troy.blogspot.com/2015/02/c-design-pattern-template-method.html) [樣板方法模式 (Template Method Pattern)](http://corrupt003-design-pattern.blogspot.com/2016/07/template-method-pattern.html) [Design Pattern: Behavioral Patterns — Template Method Pattern (模板方法模式)](https://medium.com/bucketing/behavioral-patterns-template-method-pattern-fb2a7766b501) [Template Method Design Pattern](https://sourcemaking.com/design_patterns/template_method) [Programming Patterns Overview](https://kremer.cpsc.ucalgary.ca/patterns/) --- ###### Thank you! You can find me on - [GitHub](https://github.com/s0920832252) - [Facebook](https://www.facebook.com/fourtune.chen) 若有謬誤 , 煩請告知 , 新手發帖請多包涵 # :100: :muscle: :tada: :sheep: <iframe src="https://skilltree.my/c67b0d8a-9b69-47ce-a50d-c3fc60090493/promotion?w=250" width="250" style="border:none"></iframe>