iT邦幫忙

2023 iThome 鐵人賽

0
自我挑戰組

深入淺出設計模式 (Head First Design Pattern) - 重點整理及範例分享系列 第 32

[深入淺出設計模式] Ch8 The Template Method Pattern (1) - 模板模式 範例 製作飲料

  • 分享至 

  • xImage
  •  

太久沒寫c++了 來找一下手感
所以這次是c++的範例喔~

這是書中的範例喔,假設今天我們要來寫製作飲品的食譜,分別有茶跟飲料兩種類別 (先省略方法細節):

class Tea{
    public:
        void prepareRecipe();
        void boilWater();
        void steepTeaBag();
        void pourInCup();
        void addLemon();
};

void Tea::prepareRecipe(){
    cout << "Recipe:" << endl;
    boilWater();
    steepTeaBag();
    pourInCup();
    addLemon();
}
class Coffee{
    public:
        void prepareRecipe();
        void boilWater();
        void brewCoffeeGrinds();
        void pourInCup();
        void addSugarAndMilk();
};

void Coffee::prepareRecipe(){
    cout << "Recipe:" << endl;
    boilWater();
    brewCoffeeGrinds();
    pourInCup();
    addSugarAndMilk();
}

我們可以很快地發現 boilWater() pourInCup()是相同的方法,
經過了前面章節的淬煉,這種事是絕對不允許的!
於是可以簡化後改寫:

class Beverage{
    public:
        void prepareRecipe();        
        void boilWater(); 
        void pourInCup();

    public:
        virtual void brewIngredients() = 0;
        virtual void addCondiment() = 0;
};

增加一個父類別 Beverage 將重複的方法寫進去,另外 steepTeaBag(), brewCoffeeGrinds()都是在煮主要材料的過程,所以可以先簡化成brewIngredients()的抽象方法。而addLemon(), addSugarAndMilk()也是,都屬於增加調味的方法,那就改寫成 addCondiment()。這兩個抽象方法就留給繼承後的子類別分別去定義實作。

class Coffee: public Beverage{
    public:              
        void brewIngredients();      
        void addCondiment();
};

void Coffee::brewIngredients(){
    cout << "Brew the coffee grinds" << endl;
}

void Coffee::addCondiment(){
    cout << "Add sugar and some milk" << endl; 
}
class Tea: public Beverage{
    public:       
        void brewIngredients();        
        void addCondiment();
};

void Tea::brewIngredients() {
    cout << "Steep the tea bag" << endl;
}

void Tea::addCondiment() {
    cout << "Add sugar and some milk" << endl; 
}

void Beverage::prepareRecipe(){
    cout << "Recipe:" << endl;
    boilWater();
    brewIngredients();
    pourInCup();    
    addCondiment();    
}

這就是模板模式的精髓,將程式打造成一個大致的萬用架構(父類別 Beverage),讓子類別(Coffee, Tea)按照架構自由覆寫方法實作內容,如果需要調整方法,都可以直接從父類別的方法下手,避免重複程式碼帶來的維護問題。

class Beverage{
    public:
        void prepareRecipe();        
        void boilWater(); 
        void pourInCup();
        
    public:
        virtual void brewIngredients() = 0;
        virtual void addCondiment() = 0;
        virtual bool customerNeedsCondiment() = 0;
};

void Beverage::prepareRecipe(){
    cout << "Recipe:" << endl;
    boilWater();
    brewIngredients();
    pourInCup();
    if(customerNeedsCondiment()){
        addCondiment();
    }    
}

模板方法另一個特性是可以使用hook來讓程式更佳彈性,讓子類別自由選擇是否要去實踐某個方法。例如在上述範例Beverage加上customerNeedsCondiment(),如果客人需要調味料再去執行addCondiment()
若是出現新的食譜是不能加調味料的,也可以選擇不去使用:

class MilkTea: public Beverage{
    public:       
        void brewIngredients();        
        void addCondiment();
        bool customerNeedsCondiment();
};

void MilkTea::brewIngredients(){
    cout << "Blend the tea and milk" << endl;
}

void MilkTea::addCondiment(){}

//預設不提供加調味料的選項
bool MilkTea::customerNeedsCondiment(){    
    return false;
}

來看一下實際運行狀況:

int main(){
    Coffee latte;
    latte.prepareRecipe();

    cout << endl;

    Tea signatureTea;
    signatureTea.prepareRecipe();

    MilkTea milkTea;
    milkTea.prepareRecipe();
}

輸出:

Recipe:
Boil the water
Brew the coffee grinds
Pour it into the cup
Would you like some milk or sugar? (y/n)
y
Add sugar and some milk

Recipe:
Boil the water
Steep the tea bag
Pour it into the cup
Would you like some lemon? (y/n)
n
Recipe:
Boil the water
Blend the tea and milk
Pour it into the cup


參考資料:

  1. 《深入淺出設計模式 (Head First Design Patterns) 》
  2. 書中官方程式碼傳送門
  3. 本篇程式碼傳送門

Disclaimer
因為讀的是原文版,所以難免會有翻譯詞不達意或是專有名詞上的差異,有錯誤的話歡迎在留言區一起交流!


上一篇
[深入淺出設計模式] Ch7 The Adapter Pattern (7) - 【迪米特法則】範例 超市結帳
下一篇
[深入淺出設計模式] Ch8 The Template Method Pattern (2) - 模板模式 範例 動物排序
系列文
深入淺出設計模式 (Head First Design Pattern) - 重點整理及範例分享35
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言