1. 程式人生 > >設計模式(三):模板方法模式

設計模式(三):模板方法模式

date 類定義 dimen 子類重寫 ole end recipe 增加 指導

一、星巴克服務員

1.初級服務員

假如你是一位剛入職的星巴克服務員,負責為客人泡制咖啡和茶。公司規定茶和咖啡的泡制要遵循下面的步驟:

技術分享圖片

於是你按照步驟單上的要求設計了咖啡(Coffee)和茶(Tea)並進行了制作

*****************************Coffee*****************************

/**
 * 咖啡
 * @author wuqi
 * @Date 2019/2/12 17:31
 */
public class Coffee {
    /**
     *  準備咖啡
     */
    public void
prepareRecipe(){ boilWater(); brewCoffeeGrinds(); pourInCup(); addSugarAndMilk(); } /** * 將水煮沸 */ public void boilWater() { System.out.println("Boiling water"); } /** * 用沸水沖泡咖啡 */ public void brewCoffeeGrinds(){ System.out.println(
"Dripping Coffee through filter"); } /** * 將咖啡倒進杯子裏 */ public void pourInCup(){ System.out.println("Pouring into cup"); } /** * 加糖和牛奶 */ public void addSugarAndMilk(){ System.out.println("Adding Sugar and Milk"); } }

*****************************Tea*****************************

/**
 * 茶
 * @author wuqi
 * @Date 2019/2/12 17:36
 */
public class Tea {
    /**
     * 準備茶
     */
    public void prepareRecipe(){
        boilWater();
        steepTeaBag();
        pourInCup();
        addLemon();
    }

    /**
     * 將水煮沸
     */
    public void boilWater() {
        System.out.println("Boiling water");
    }

    /**
     * 用沸水浸泡茶葉
     */
    public void steepTeaBag(){
        System.out.println("Steeping the tea");
    }

    /**
     * 將茶倒進杯子裏
     */
    public void pourInCup(){
        System.out.println("Pouring into cup");
    }

    /**
     * 加檸檬
     */
    public void addLemon(){
        System.out.println("Adding Lemon");
    }
}

*****************************制作茶和咖啡*****************************

/**
 * 沒有使用模板方法模式測試
 * @author wuqi
 * @Date 2019/2/12 17:44
 */
public class NoTemplateTest {
    public static void main(String[] args) {
        System.out.println("Making coffee...");
        Coffee coffee = new Coffee();
        coffee.prepareRecipe();

        System.out.println();
        System.out.println("Making Tea...");
        Tea tea = new Tea();
        tea.prepareRecipe();
    }
}

制作結果:

技術分享圖片

你成功的將咖啡制作了出來,但是一個高級服務員從你身旁經過時看了一眼你的制作代碼,嘲諷的說到:你這樣制作的咖啡和茶有太多的重復代碼,如果以後再讓你制作另一種飲料,制作的步驟應該還是類似的,你還要再把重復的代碼寫一遍,最要命的是你竟然沒有對這些飲料進行抽象。你雖然不高興,但是別人把你設計的代碼存在的問題一一指了出來,你還是比較感激的。你恭敬的說到:你說的對,我會改進一下的。

2.高級服務員

根據之前嘲諷你的高級服務員的指導,看著你之前所寫的代碼,你陷入了沈思:首先,我應該將咖啡和茶抽象出來,這兩種飲料都含有咖啡因,就抽象成CaffeineBeverage吧。然後咖啡和茶有兩個完全相同的方法boilWater和pourIntoCup,將這兩個方法放在抽象類中。這還不夠,對於prepareRecipe()來說,咖啡和茶都是遵循著相同的步驟,而且是定死的步驟,不允許我們修改,只是步驟具體實現的方式不一樣,這些方式是由具體的類(咖啡和茶)決定的,所以我可以將這個方法放在抽象類中作為一個模板(template),並且不允許子類重寫,子類只可以重寫某些步驟具體的實現方式,就是怎麽泡飲料,怎麽加調料,這樣的話我就需要將咖啡的brewCoffeeGrinds()和steepTeaBag()泛化成brew()這個名字,都是沖泡,讓子類具體去決定怎麽個泡法,而對於咖啡的addSugarAndMilk()和茶的addLemon()則可以泛化成addCondiments(),就是加調料,具體加什麽調料也由子類去決定。按照這個思路你畫出了相關的UML類圖

技術分享圖片

按照這個UML類圖,你寫下了改進後的代碼:

*****************************CaffeineBeverage*****************************

/**
 * 咖啡因飲料
 * @author wuqi
 * @Date 2019/2/12 17:49
 */
public abstract class CaffeineBeverage {

    /**
     * 抽象類定義模板,定義準備咖啡因飲料的具體步驟
     * 該步驟為final,不允許子類修改
     */
    public final void prepareRecipe(){
        boilWater();
        brew();
        pourInCup();
        addCondiment();
    }

    /**
     * 將水煮沸
     */
    public void boilWater() {
        System.out.println("Boiling water");
    }

    /**
     * 沖泡
     */
    public abstract void brew();

    /**
     * 將飲料倒進杯子裏
     */
    public void pourInCup(){
        System.out.println("Pouring into cup");
    }

    /**
     * 加調料
     */
    public abstract void addCondiment();
}

*****************************Coffee*****************************

/**
 * 咖啡
 * @author wuqi
 * @Date 2019/2/12 17:54
 */
public class Coffee extends CaffeineBeverage {
    /**
     * 用水沖泡咖啡
     */
    @Override
    public void brew() {
        System.out.println("Dripping Coffee through filter");
    }

    /**
     * 添加糖和牛奶
     */
    @Override
    public void addCondiment() {
        System.out.println("Adding Sugar and Milk");
    }
}

*****************************Tea****************************

/**
 * 茶
 * @author wuqi
 * @Date 2019/2/12 17:56
 */
public class Tea extends CaffeineBeverage {
    /**
     * 用水浸泡茶葉
     */
    @Override
    public void brew() {
        System.out.println("Steeping the tea");
    }

    /**
     * 添加檸檬
     */
    @Override
    public void addCondiment() {
        System.out.println("Adding Lemon");
    }
}

*****************************測試制作咖啡和茶****************************

/**
 * 模板方法測試
 * @author wuqi
 * @Date 2019/2/12 17:58
 */
public class TemplateTest {
    public static void main(String[] args) {
        System.out.println("Making Coffee...");
        Coffee coffee = new Coffee();
        coffee.prepareRecipe();

        System.out.println();
        System.out.println("Making Tea...");
        Tea tea = new Tea();
        tea.prepareRecipe();
    }
}

運行結果:

技術分享圖片

運行改進後的代碼,我們依然成功制作了咖啡和茶。但是改進後的代碼更加簡潔了,復用度更高,而且以後如果再多出一個咖啡因飲料讓我們去制作,我們只需要讓新加的類繼承CaffeineBeverage,並重寫brew()和addCondiments方法即可,再也不用每次都添加很多重復的代碼了,這樣就使我們的系統非常的具有彈性。你非常的滿意,看著改進後的代碼,你突然想到這不就是之前我學習設計模式中的模板方法模式嗎!想不到在這裏用到了。這次,你老板從你身旁經過,看著你制作咖啡的代碼,滿意的點了點頭,說到:不錯,你這個咖啡制作的代碼非常好,你用到了模板方法模式,現在開始,我正是聘用你為高級服務員了。你非常詫異也非常高興的說了聲:謝謝老板!隨後你老板又說到:我們的茶後面不準備再加調料了,正好模板方法的鉤子可以派上用場,你去改進一下吧,也當作你作為高級服務員的最後一道考核。你恭敬的回了句:是的!老板!

3.神奇的鉤子

你努力的回憶著模板方法模式的知識,模板方法模式就是在抽象的基類中定義了一個模板,這個模板中規定了完成該方法所需的具體算法,對應於我們制作飲料的各個步驟,子類需要按照這個步驟去執行該方法,步驟中的一些實現方式可以延遲到子類去決定。而鉤子是穿插在算法當中的一個可選的部分,它有默認的實現,鉤子可以由子類實現並決定是否啟用。你又畫了個UML類圖來表示加上鉤子後的飲料制作方式。

技術分享圖片

根據這個UML類圖,你寫下了加上鉤子的代碼

*****************************CaffeineBeverage****************************

/**
 * 咖啡因飲料
 * @author wuqi
 * @Date 2019/2/12 17:49
 */
public abstract class CaffeineBeverage {
    /**
     * 抽象類定義模板,定義準備咖啡因飲料的具體步驟
     * 該步驟為final,不允許子類修改
     */
    public final void prepareRecipe(){
        boilWater();
        brew();
        pourInCup();
        if(needAddCondiments()){
            addCondiment();
        }
    }

    /**
     * 鉤子
     * @return
     */
    protected boolean needAddCondiments(){
        return false;
    }

    /**
     * 將水煮沸
     */
    public void boilWater() {
        System.out.println("Boiling water");
    }

    /**
     * 沖泡
     */
    public abstract void brew();

    /**
     * 將飲料倒進杯子裏
     */
    public void pourInCup(){
        System.out.println("Pouring into cup");
    }

    /**
     * 加調料
     */
    public abstract void addCondiment();
}

*****************************Coffee****************************

/**
 * 咖啡
 * @author wuqi
 * @Date 2019/2/12 17:54
 */
public class Coffee extends CaffeineBeverage {
    /**
     * 用水沖泡咖啡
     */
    @Override
    public void brew() {
        System.out.println("Dripping Coffee through filter");
    }

    /**
     * 添加糖和牛奶
     */
    @Override
    public void addCondiment() {
        System.out.println("Adding Sugar and Milk");
    }

    /**
     * 子類覆蓋鉤子
     * @return
     */
    @Override
    public boolean needAddCondiments() {
        return true;
    }

}

*****************************Tea****************************

/**
 * 茶
 * @author wuqi
 * @Date 2019/2/12 17:56
 */
public class Tea extends CaffeineBeverage {
    /**
     * 用水浸泡茶葉
     */
    @Override
    public void brew() {
        System.out.println("Steeping the tea");
    }

    /**
     * 添加檸檬
     */
    @Override
    public void addCondiment() {
        System.out.println("Adding Lemon");
    }
}

*****************************TemplateTest****************************

/**
 * 模板方法測試
 * @author wuqi
 * @Date 2019/2/12 17:58
 */
public class TemplateTest {
    public static void main(String[] args) {
        System.out.println("Making Coffee...");
        Coffee coffee = new Coffee();
        coffee.prepareRecipe();

        System.out.println();
        System.out.println("Making Tea...");
        Tea tea = new Tea();
        tea.prepareRecipe();
    }
}

測試結果:

技術分享圖片

可以看到現在制作茶已經不會再添加檸檬了,因為模板中鉤子默認的是不加調料的,而Tea中沒有重寫鉤子,所以茶現在是不加調料了。你把改進後的代碼交給老板看,你老板非常滿意的點了點頭。從此,你開心的做起了一名高級服務員。

二、定義模板方法

1.模板方法的概念

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

2.UML類圖

技術分享圖片

3.模板方法模式中存在的OO設計原則

1.好萊塢原則

模板方法模式中用到OO設計原則中的好萊塢原則,別調用(打電話給)我們,我們會(打電話給)調用你。

好萊塢原則可以給我們一種防止“依賴腐敗的方法”。當高層組件依賴低層組件,而低層組件又依賴高層組件,而高層組件又依賴邊側組件,而邊側組件又依賴低層組件時,依賴腐敗就發生了。在這種情況下,沒人能輕易搞懂系統是怎麽設計的。在好萊塢原則下,我們允許低層組件將自己掛鉤到系統上,但是高層組件會決定什麽時候和怎樣使用這些低層組件。換句話說,高層組件對待低層組件的方式是“別調用我們,我們會調用你”。

技術分享圖片

2.模板方法模式和好萊塢原則

模板方法模式就是在抽象基類中定義了一個模板,規定了子類執行的具體步驟和時序,子類只是決定某些步驟的實現方式。抽象基類是高層,子類是低層,這剛好符合好萊塢原則。

三、模板方法模式的應用場景

模板方法模式適用需要定義一套固定的模板,模板中的算法是不更改的,但是某些具體的實現方式可以由子類決定的場景

四、模板方法模式的優缺點

1.優點

  1. 模板方法模式通過抽象類,可以將代碼的復用最大化
  2. 具體的算法只存在於模板中,讓系統的修改變得容易,系統會比較有彈性

2.缺點

需要為每一個基本方法的不同實現提供一個子類,如果父類中可變的基本方法太多,將會導致類的個數增加,系統更加龐大,設計也更加抽象。

設計模式(三):模板方法模式