今天是五.四青年節,祝大家節日快樂。看著今天這標題就有食欲,夏天到了,醋溜土豆絲和清炒苦瓜適合夏天吃,好吃不上火。這兩道菜大部分人都應該吃過,特別是醋溜土豆絲,作為“魯菜”的代表作之一更是為大眾所熟知,醋溜土豆絲,好吃不上火。清炒苦瓜這道菜好啊,更是夏天必備之良菜,其功效在此就不做過多贅述了。言歸正 ...
今天是五.四青年節,祝大家節日快樂。看著今天這標題就有食欲,夏天到了,醋溜土豆絲和清炒苦瓜適合夏天吃,好吃不上火。這兩道菜大部分人都應該吃過,特別是醋溜土豆絲,作為“魯菜”的代表作之一更是為大眾所熟知,醋溜土豆絲,好吃不上火。清炒苦瓜這道菜好啊,更是夏天必備之良菜,其功效在此就不做過多贅述了。言歸正傳,上篇博客我們從“小弟”中學習了“外觀模式”,我們也把“外觀模式”戲稱為“小弟模式”。今天我們要從醋溜土豆絲和清炒苦瓜的製作過程中來學習一下我們今天博客的主題“模板方法模式”(Template Method Pattern)。
說到模板方法模式,如果你看過之前發表的重構相關的博客的話,應該對模板方法模式並不陌生。在《代碼重構(五):繼承關係重構規則》這篇博客中第三部分,其實是使用的“模板方法模式”進行重構的,在重構規則裡邊我們稱之為“Form Template Method (構造模板函數)”,這個模板函數就是我們本篇博客中的模板方法。今天我們要從另一個角度來看一下“模板方法模式”,並從“醋溜土豆絲”和“清炒苦瓜”的製作實例中來學習一下“模板方法模式”。在本篇博客中,你不僅是一位Programer,還是一位Cook。
老規矩,在博客的開頭,我們先給出“模板方法模式”的定義。如果你是小白,定義看不懂沒關係,因為定義一般都比較難理解。你可以看完下方的具體實例後在回頭看這個定義即可。模板方法的定義如下:
模板方法模式:在一個方法中定義了一個演算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類可以在不改變演算法結構的情況下,重新定義演算法中的某些步驟。
一、開始炒菜
接下來我們將要開始製作我們的醋溜土豆絲和清炒苦瓜這兩道菜,當然在本篇博客的第一部分我們不會使用我們的模板方法來炒菜的。我們在該部分先給出常規的做菜的方法,然後我們會對其分析,最終會使用我們的“模板方法模式”來進行炒菜。當然在此我們不是真的用鍋炒菜了,而是用我們的代碼來炒菜,走起。
1.炒一個醋溜土豆絲
接下來我們要炒一個醋溜土豆絲,醋溜土豆絲好吃,做起來又簡單。還是那句話,醋溜土豆絲,好吃不上火。因為炒“醋溜土豆絲”我們是在一個類中完成的,所以在此就不畫類圖了,等下發使用模板方法模式後,在給出相應的類圖。下方的FryShreddedPotatoes類就是我們做“醋溜土豆絲”的類。其中給出了做醋溜土豆絲的一系列的步驟,並且在fryShreddedPotatoes()函數中對這一系列的函數進行了組合。
上面完成的這個類,我們就可以將上述這個類進行實例化並調用fryShreddedPotatoes()來炒一盤醋溜土豆絲了。下方我們就將FryShreddedPotatoes進行實例化,然後炒了一盤醋溜土豆絲。
2、來一盤“清炒苦瓜”
上面土豆絲炒完了,已出鍋。接下來我們還要來一盤“清炒苦瓜”,當然“清炒苦瓜”的做法和上面“醋溜土豆絲”差不過,不過有些步驟還是不同的。當然你在炒苦瓜的時候不能放醋了,還有在炒苦瓜的時候你放的是苦瓜片而不是土豆絲了,這些都是不同的。當然兩者的菜名也不一樣呢。下方這個類就是我們“清炒苦瓜”的類,你可以將該類進行實例化然後去做一盤屬於你的清炒苦瓜。大體上一看,下方的代碼與上面我們醋溜土豆絲的類的步驟差不多,但是具體細節,以及一些步驟所調用的方法所不同。下方就是我們清炒苦瓜的類,如下所示:
接著就是來實例化上面“清炒苦瓜”的類,然後來一盤清炒苦瓜。下方是我們創建的上面的類的對象,然後去調用炒苦瓜的方法。下方是調用方式和輸出結果,經過下方的過程,我們就可以出鍋一盤清炒苦瓜了,如下所示:
二、使用“模板方法”來炒菜
在上面兩種炒菜的方法中,我們不難看出炒醋溜土豆絲和清炒苦瓜中的類中有好多重覆的代碼。在我們重構系列中的博客中我們已經提到切記重覆代碼,所以我們要對上面的兩個類進行代碼的重構,而重構的方式就是使用“模板方法模式”來進行重構。本質上就是將變的部分與不變的部分進行分離,在該實例中變的部分就是某些步驟的具體細節,而不變的是執行的步驟和部分步驟中的內容。
在該炒菜實例中整個炒菜的流程是不變的,該流程中的一些步驟也是不變的。變化的就是報菜名方法中所報的菜名不同,然後是所炒的菜不同,一個炒的是土豆絲一個炒的是苦瓜,最後是加的調料不同。這樣一分析,我們可以將不變的方法放到父類中,將炒菜這個不變的步驟封裝成“模板方法”,具體不變的某些步驟(比如放油等)可以放在延展中實現。該部分的代碼結構些微的有些複雜,所以我們會給出相應的類圖。
1.重構後的類圖如下(使用了“模板方法模式”)
首先我們會給出重構後的類圖,使用“模板方法模式”重構後的類圖如下所示。 FryVegetablesType是我們創建的炒菜協議,其中定義了“醋溜土豆絲”和“清炒苦瓜”所有的方法,不過我們對兩者不同的方法進行了重新命名,讓其統一。因為兩者不同的方法所實現的東西大體一致,比如之前清炒苦瓜中的放苦瓜的方法putBitterGourd(),我們重命名成了“放蔬菜”的putVegetables()方法。putVegetables()方法用於“醋溜土豆絲”中也是可以的,因為putVegetables()在“清炒苦瓜”中放的是苦瓜,在炒土豆絲中放的是土豆絲。
在FryVegetablesType協議的延展中,我們給出了相同的預設實現,比如模板方法(fry())、放油(putSomeOil())、放蔥花(putSomeGreenOnion())、出鍋(outOfThePan())等方法。在炒這兩個菜時延展中的方法是不變的。而我們炒土豆絲和炒苦瓜中實現的方法是兩者不同的地方,類圖如下:
2.炒菜介面與介面延展代碼實現
根據上述的類圖,我們可以給出炒菜介面以及介面延展的代碼實現。接下來要做的事情就是將不變的部分提取到介面和介面的延展中,下方就是我們提取的炒菜的介面FryVegetablesType以及該介面對應的延展。FryVegetablesType協議中給出了模板方法fry(),以及炒菜的步驟(也就是炒菜的演算法)。在該介面的延展中,模板方法fry()的預設實現調用和這些步驟,並且給出了一些不變的步驟的實現。具體代碼如下所示:
3.“醋溜土豆絲”和“清炒苦瓜”的具體實現
下方代碼段是醋溜土豆絲和清炒苦瓜在模板方法模式中的代碼實現。從下方代碼我們不難看出,兩者都遵循了FryVegetablesType協議,並且擁有該協議的預設擴展。我們知道預設擴展中的代碼類似於抽象類的預設實現,為子類所共有,所以下方兩個類只給出了不同的步驟。比如在醋溜土豆絲中放的作料是鹽和醋,而在清炒苦瓜中放的作料是鹽。當然兩個子類中其他實現的兩個方法也是不同之處。無論子類怎麼給出預設實現的步驟,我們在預設延展中給出的模板方法是不變的,也就是炒菜的具體步驟是不變的。這就是模板方法模式,模板方法不關心每個步驟的具體細節,只關心步驟執行的順序,這就是所謂的模板方法是對演算法的封裝,而不是對具體計算細節的封裝。
使用模板方法的一個顯而易見的好處就是減少了代碼冗餘,將變化的部分與不變的部分進行了分離。在該示例中就是將不變的部分放在了協議的預設延展中,將變化的部分放在了子類中。這就是“模板方法模式”。
4、“模板方法模式”的測試用例
接下來我們要對上述的示例進行測試,下方就是我們模板方法的測試用例,其實下方的測試用例與之前沒有使用模板方法時的測試用例類似,只是炒菜調用的方法有所不同。雖然調用的方法有些差異,此處的差異僅僅是函數名稱的差異,該函數所做的事情沒有變化。下方就是我們重構後的代碼的測試用例以及運行結果。從結果中看出,與我們之前沒有使用模板方法的測試用例的輸出結果一致。這就是我們之前在“重構”系列博客中經常提到的改變代碼內部的結構,而不改變代碼對外調用的介面。
本篇博客與之前我們類重構中的“構建模板方法”的部分較為類似,都是介紹的模板方法模式。而在前面我們是從重構的角度來使用模板方法模式的,而今天的博客的主題不是重構而是我們的“模板方法模式”。由於篇幅有限,我們的今天的博客就先到這兒,後面還會繼續更新其他Swift版的設計模式。
今天博客中的代碼在github上的分享地址為:https://github.com/lizelu/DesignPatterns-Swift