Java 设计模式之模板方法模式
一、了解模板方法模式
1.1 什么是模板方法模式
模板方法模式 Template Method Parrern)在一個(gè)方法中定義了一個(gè)算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類可以在不改變算法結(jié)構(gòu)的情況下,重新定義方法中的某些步驟。
模板就是一個(gè)方法,更具體的說,這個(gè)方法將算法定義為一組步驟,其中的任何步驟都可以是抽象的,由子類負(fù)責(zé)實(shí)現(xiàn)。這可以確保算法的結(jié)構(gòu)保持不變,同時(shí)由子類提供部分實(shí)現(xiàn)。
1.2 模板方法模式組成結(jié)構(gòu)
- 抽象類(AbstractClass):包含模板方法,定義了算法的骨架。專注于算法本身。
- 具體類(ConcreteClass):實(shí)現(xiàn)抽象類中的抽象方法,完成算法的實(shí)現(xiàn)。
1.3 模板方法 UML 圖解
1.4 模板方法的適用場(chǎng)景
- 在某些類的算法中,用了相同的方法,造成代碼的重復(fù)。
- 控制子類擴(kuò)展,子類必須遵守算法規(guī)則。
二、模板方法模式具體應(yīng)用
2.1 問題描述
茶與咖啡:有的人喜歡喝茶,而有的人 喜歡喝咖啡。茶與咖啡 之間有什么共同的成分嗎?答案是咖啡因 (怎么樣,沒想到吧)。不止是這樣,茶與咖啡的沖泡方式也十分類似。如下
茶沖泡法:
咖啡沖泡法:
現(xiàn)在我們來設(shè)計(jì)代碼,完成茶與咖啡的沖泡。
2.2 簡(jiǎn)單設(shè)計(jì)實(shí)現(xiàn)
因?yàn)榇a比較簡(jiǎn)單,所以這里只提供代碼實(shí)現(xiàn)的設(shè)計(jì)圖。從上面的設(shè)計(jì)圖中,我們應(yīng)該能發(fā)現(xiàn)一個(gè)問題:存在著一些重復(fù)的代碼 (boilWater() 與 pourInCup())。所以我們要重新考慮一下設(shè)計(jì),來避免這個(gè)問題。
PS:有一個(gè)重要的設(shè)計(jì)原則:找出應(yīng)用中可能需要變化之處,把它們獨(dú)立出來,不要和那些不需要變化的代碼混在一起。
于是我們可能想到使用繼承,把不變的代碼方法超類中,把變化的代碼放在子類中去實(shí)現(xiàn)。
2.3 使用繼承
這種通過繼承的設(shè)計(jì)感覺還不錯(cuò),可以很好的解決問題。但是我們仔細(xì)想想,沖泡茶與咖啡是不是還存在一些共同點(diǎn)呢?其實(shí) brewCoffeeGrinds() 與 steepTeaBag() 的動(dòng)作很類似,因此我們可以使用 brew() 來統(tǒng)一這兩個(gè)方法;addSugarAndMilk() 與 addLemon() 也很類似,我們用 addCondiments() 來統(tǒng)一。
2.4 模板方法模式登場(chǎng)
(1) 模板方法 UML 設(shè)計(jì) (可參考模板方法 UML 圖解)
(2) 模板方法代碼實(shí)現(xiàn)
抽象 CaffineBeverage 類
package com.jas.templatemethod;public abstract class CaffineBeverage {/*** 模板方法,用作一個(gè)算法的模板,用于制作茶與咖啡* 并定義為 final 的,防止子類覆蓋*/final void prepareRecipe(){boilWater();brew();pourInCup();addCondiments();}void boilWater(){System.out.println("煮沸水!");}abstract void brew();void pourInCup(){System.out.println("倒進(jìn)杯子里!");}abstract void addCondiments(); }具體 Tea 類
package com.jas.templatemethod;public class Tea extends CaffineBeverage {@Overridevoid brew() {System.out.println("用沸水浸泡茶葉!");}@Overridevoid addCondiments() {System.out.println("加入檸檬!");} }具體 Coffee 類
package com.jas.templatemethod;public class Coffee extends CaffineBeverage {@Overridevoid brew() {System.out.println("用沸水浸泡咖啡!");}@Overridevoid addCondiments() {System.out.println("加入糖與牛奶!");} }測(cè)試類
package com.jas.templatemethod;public class Test {public static void main(String[] args) {CaffineBeverage coffee = new Coffee();CaffineBeverage tea = new Tea();coffee.prepareRecipe();System.out.println();tea.prepareRecipe();} }/*** 輸出* 煮沸水!* 用沸水浸泡咖啡!* 倒進(jìn)杯子里!* 加入糖與牛奶!** 煮沸水!* 用沸水浸泡茶葉!* 倒進(jìn)杯子里!* 加入檸檬!*/(3) 問題總結(jié)
CaffineBeverage 類主導(dǎo)一切,它擁有算法,并保護(hù)這個(gè)算法 (模板方法定義為 final 的)。對(duì)于子類來說,因?yàn)?CaffineBeverage 類的存在,代碼能夠最大程度的復(fù)用。并且算法存在于模板方法中,更易于修改,并且擴(kuò)展性更強(qiáng)。CaffineBeverage 類本身專注與算法本身,由子類提供完整實(shí)現(xiàn)。
2.5 對(duì)模板方法進(jìn)行掛鉤
(1) 什么是鉤子
鉤子 (Hook) 是一種被聲明抽象類中的方法,但是只有空的或默認(rèn)的實(shí)現(xiàn)。
鉤子的存在,可以讓子類有能力對(duì)算法的不同點(diǎn)進(jìn)行掛鉤。到底要不要掛鉤,由子類自行決定。
(2) 將鉤子應(yīng)用到代碼中去
改造 CaffineBeverage
package com.jas.templatemethod;public abstract class CaffineBeverageWithHook {final void prepareRecipe(){boilWater();brew();pourInCup();/** 這里加上鉤子的判斷,只有鉤子返回值為 true 時(shí),表示顧客想要加調(diào)料,才添加調(diào)料*/if(customerWantsCondiments()){addCondiments();}else {System.out.println("不添加任何調(diào)料");}}void boilWater(){System.out.println("煮沸水!");}abstract void brew();void pourInCup(){System.out.println("倒進(jìn)杯子里!");}abstract void addCondiments();/*** 這是一個(gè)鉤子,子類可以自行選擇覆蓋,但是也可以不這么做。* @return*/boolean customerWantsCondiments(){return true;} }改造 Coffee
package com.jas.templatemethod;import java.util.Scanner;public class CoffeeWithHook extends CaffineBeverageWithHook {@Overridevoid brew() {System.out.println("用沸水浸泡咖啡!");}@Overridevoid addCondiments() {System.out.println("加入糖與牛奶!");}@Overrideboolean customerWantsCondiments() {/** 只有當(dāng)用戶輸入的是 'y' 時(shí),返回 true,才添加調(diào)料 */if("y".equals(getUserInput())){return true;}else {return false;}}/*** 獲得用戶在控制臺(tái)輸入的數(shù)據(jù)* @return*/private String getUserInput(){String answer = null;System.out.println("你是否想要添加調(diào)料,請(qǐng)輸入 (y/n)");Scanner read = new Scanner(System.in);answer = read.nextLine();return answer;} }測(cè)試
package com.jas.templatemethod;public class Test {public static void main(String[] args) {CaffineBeverageWithHook coffeeWithHook = new CoffeeWithHook();coffeeWithHook.prepareRecipe();}/*** 輸出* 煮沸水!* 用沸水浸泡咖啡!* 倒進(jìn)杯子里!* 你是否想要添加調(diào)料,請(qǐng)輸入 (y/n)* y* 加入糖與牛奶!*/ }(3) 鉤子總結(jié)
在實(shí)際工作中,如果算法的這個(gè)部分是可選的,那么你可以選擇使用鉤子。
鉤子可以讓子類實(shí)現(xiàn)算法中可選的部分,或者在鉤子對(duì)子類的實(shí)現(xiàn)并不重要時(shí),子類可以對(duì)此鉤子置之不理。
鉤子可以使子類能夠有機(jī)會(huì)對(duì)模板方法中即將發(fā)生的步驟做出反應(yīng)。
三、 模板方法模式總結(jié)
3.1 模板方法優(yōu)缺點(diǎn)總結(jié)
優(yōu)點(diǎn)
- 封裝不變部分,擴(kuò)展可變部分。
- 提取公共代碼,便于維護(hù)。
- 行為由父類控制,子類實(shí)現(xiàn)。
缺點(diǎn)
每一個(gè)不同的實(shí)現(xiàn)都需要一個(gè)子類來實(shí)現(xiàn),導(dǎo)致類的個(gè)數(shù)增加,使得系統(tǒng)更加龐大。
3.2 模板方法模式知識(shí)點(diǎn)總結(jié)
- 模板方法”定義了算法的實(shí)現(xiàn)步驟,并把一些步驟的實(shí)現(xiàn)推遲到子類中。
- 模板方法模式為我們提供了一種代碼復(fù)用的技巧。
- 模板方法的抽象類可以定義具體方法、抽象方法和鉤子。抽象方法由其子類實(shí)現(xiàn)。
- 鉤子是一種方法,它在抽象類中不做事,或者只做一些默認(rèn)的事,子類可以選擇要不要覆蓋它。
- 為了防止子類覆蓋模板方法,可以將模板方法定義為 final 的。
- 可以把工廠方法理解為模板方法的一種特殊版本。
PS:點(diǎn)擊了解更多設(shè)計(jì)模式 http://blog.csdn.net/codejas/article/details/79236013
參考文獻(xiàn)
《Head First 設(shè)計(jì)模式》
總結(jié)
以上是生活随笔為你收集整理的Java 设计模式之模板方法模式的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 毕业生在武汉自如租房有补贴吗?
- 下一篇: Java 设计模式之迭代器模式