1. 程式人生 > >Head First設計模式——模板方法模式

Head First設計模式——模板方法模式

前言:本篇我們講解模板方法模式,我們以咖啡和茶的沖泡來學習模板方法。關於咖啡另一個設計模式例子也以咖啡來講解,可以看下:Head First設計模式——裝飾者模式

廢話不多說,開始進入模板方法模式。

一、沖泡咖啡和茶

沖泡咖啡的步驟:

(1)把水燒開

(2)用開水沖泡咖啡

(3)把咖啡到進杯子

(4)加糖和牛奶

沖泡茶的步驟

(1)把水燒開

(2)用開水浸泡茶

(3)把茶到進杯子

(4)加檸檬

實現沖泡咖啡和茶的兩個類

    public class Coffee
    {
        public void PrepareRecipe()
        {
            BoilWater();
            BrewCoffeeGrinds();
            PourInCup();
            AddSugarAndMilk();
        }
        public void BoilWater()
        {
            Console.WriteLine("燒開水");
        }

        public void BrewCoffeeGrinds()
        {
            Console.WriteLine("開水沖泡咖啡");
        }

        public void PourInCup()
        {
            Console.WriteLine("咖啡到進杯子");
        }

        public void AddSugarAndMilk()
        {
            Console.WriteLine("加糖和牛奶");
        }
    }

  

    public class Tea
    {
        public void PrepareRecipe() {
            BoilWater();
            SteepTeaBag();
            PourInCup();
            AddLemon();
        }
        public void BoilWater()
        {
            Console.WriteLine("燒開水");
        }

        public void SteepTeaBag()
        {
            Console.WriteLine("開水浸泡茶");
        }

        public void PourInCup()
        {
            Console.WriteLine("茶到進杯子");
        }

        public void AddLemon()
        {
            Console.WriteLine("加檸檬");
        }
    }

  我們從這兩個類發現了一些重複的程式碼,有重複的程式碼表示我們需要理一下我們的實現方式或者設計。咖啡和茶的程式碼在第1步和第3步的方法都是一樣,其他兩個方法是各自獨有的,那我們可以將相同的方法抽象到同一個基類中。可能類似如下方式

  咖啡和茶自己特有的方法,放在自己的類中,每個子類都覆蓋PrepareRecipe()方法,並實現自己的沖泡。

 

二、更進一步的改進

  通過上面的繼承改造我們感覺是否還是有些共同方法沒有封裝乾淨,還有什麼共同點沒有封裝了。在咖啡和茶類中都有PrepareRecipe()方法,他們的步驟都是一樣的,只是特定步驟的實現方式不一樣,那我們如何抽象PrepareRecipe()方法?我們從每個子類中逐步抽離

① 遇到的第一個問題,咖啡和茶的沖泡浸泡方法不一樣,所以給他們一個新的方法名稱Brew(),然後不管是沖泡還是浸泡都用這個名稱。同樣的加東西我們也類似的取一個新的方法名稱來解決,就叫AddCondiments()。這樣一來PrepareRecipe()方法就改造成這樣:

        public void PrepareRecipe()
        {
            BoilWater();
            Brew();
            PourInCup();
            AddCondiments();
        }

② 我們現在有了新的PrepareRecipe()方法,需要讓他符合程式碼,所以我們再改造父類CoffeineBeverage

    public abstract class CoffeineBeverage
    {
        public  void PrepareRecipe()
        {
            BoilWater();
            Brew();
            PourInCup();
            AddCondiments();
        }

        public abstract void AddCondiments();
        public abstract void Brew();

        private void BoilWater()
        {
            Console.WriteLine("燒開水");
        }

        private void PourInCup()
        {
            Console.WriteLine("茶到進杯子");
        }
    }

③ 最後我們需要處理下咖啡和茶類讓它們繼承父類,實現自己的特有方法。

    public class Coffee: CoffeineBeverage
    {
        public override void Brew()
        {
            Console.WriteLine("開水沖泡咖啡");
        }
        public override void AddCondiments()
        {
            Console.WriteLine("加糖和牛奶");
        }
    }


    public class Tea:CoffeineBeverage
    {
        public override void Brew()
        {
            Console.WriteLine("開水浸泡茶");
        }
        public override void AddCondiments()
        {
            Console.WriteLine("加檸檬");
        }
       
    }

三、模板方法模式

 基本上,通過第二步的改進我們實現的就是模板方法模式。PrepareRecipe()是我們的抽象模板方法。

(1)它是一個方法

(2)它用作一個演算法的模板,在本例中,演算法就是用來製作飲料。在這個模板中演算法內的每一個步驟都被一個方法代表。某些方法由父類處理,有些則由子類處理,需要子類處理的方法在父類中被定義成抽象方法。

定義:

模板方法模式:在一個方法中定義一個演算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類可以在不改變演算法結構的情況下,重新定義演算法中的某些步驟。

類圖:

 

四、模板鉤子

鉤子是一種被宣告在抽象類中的方法,但只有空的或者預設的實現。鉤子的存在可以讓子類有能力對演算法的不同點進行掛鉤。要不要掛鉤,由子類決定。

例如茶裡面需不需要加檸檬可以子類自己決定

     public abstract class CoffeineBeverage
    {
        public void PrepareRecipe()
        {
            BoilWater();
            Brew();
            PourInCup();
            if (CustomerWantsCondiments())
            {
                AddCondiments();
            }
        }

        public virtual bool CustomerWantsCondiments()
        {
            return true;
        }

        public abstract void AddCondiments();
        public abstract void Brew();

        private void BoilWater()
        {
            Console.WriteLine("燒開水");
        }

        private void PourInCup()
        {
            Console.WriteLine("茶到進杯子");
        }
    }

  不加檸檬的茶

    public class Tea : CoffeineBeverage
    {
        public override bool CustomerWantsCondiments()
        {
            return false;
        }
        public override void Brew()
        {
            Console.WriteLine("開水浸泡茶");
        }
        public override void AddCondiments()
        {
            Console.WriteLine("加檸檬");
        }

    }

  測試:

五、好萊塢原則

模板方法模式當中涉及到好萊塢原則

好萊塢原則:別調用(打電話給)我們,我們會呼叫(打電話給)你。

好萊塢原則是一種防止“依賴腐敗”的方法。當高層元件依賴低層元件,低層元件又依賴高層元件,依賴腐敗就產生了。在這種情況下,很難有人搞懂系統的設計和維護難度加大。

在好萊塢原則之下,我們允許低層元件將自己掛鉤到系統上,高層元件會決定什麼時候和怎樣使用這些低層元件。換句話說,高層元件對待低層元件方式就是“別調用我們,我們會呼叫你”。

而我們的模板方法模式是如何遵循這一設計原則的:CoffeineBeverage是我們的高層元件,它控制沖泡的演算法,只有在需要子類實現某個方法是才會呼叫子類。而子類Coffee和Tea不會直接呼叫抽象的父類,只是簡單用來提供實現一些自身的細節。