設計模式-模板方法模式.md

weixin_33866037發表於2017-03-28

一、什麼是模板方法模式?
模板方法定義了一個操作中的演算法骨架,將一些具體造作步驟延遲到子類中實現,使子類在不改變一個演算法結構的同時可以重新定義該演算法的某些特定步驟。

比如,我們到銀行辦理業務,都會經過這麼幾個步驟:

step1:進門取號;

step2:填寫單據;

step3:等待叫號;

step5:視窗辦理;

以上這5個步驟,是每個客戶辦理業務都要做的事情,那麼他們合起來就是一個框架。同時,每個客戶辦理的業務又會有所不同,比如step2和step4,那麼這些差異化的操作放在子類中實現。

簡而言之,模板方法的基本思想就是,將共同的操作放在父類中實現,差異化的操作放在子類中實現。

二、實現方法

以下面的例子來說明。

咖啡的泡法:

                        1.把水煮沸(boilWater);

                        2.用沸水沖泡咖啡(brew);
                            3.把咖啡倒進杯子(pourInCunp);
                            4.加糖和牛奶(addCondimentis);

茶的泡法:

                        1.把水煮沸;

                        2.用沸水浸泡茶葉;

                        3.把茶倒進杯子;

                        4.加檸檬;

可以看到,兩種飲料的泡法中,第一步和第三部的操作是相同的,區別僅僅是在第二步和第四步。



首先,定義一個抽象的基類,該類封裝了飲料的泡製:

package com.dengjz.tmp;

/**

  • 抽象的基類,定義了飲料的泡製方法,為所有子類提供一個演算法框架

  • @author dengjunzhen

  • @date 2016年2月2日上午10:27:52

*/

public abstract class RefreshBeverage {

/**

 * 泡製進料的具體方法,封裝了所有子類共同遵循的演算法框架

 * @author dengjunzhen

 * @date 2016年2月2日上午10:30:39

 */

public final void prepareBeverageTemplate() {

    // 步驟1:將水煮沸

    boilWater();

    // 步驟2:泡製進料

    brew();

    // 步驟3:將飲料倒入杯中

    pourInCunp();

    // 步驟4:加入調料

    addCondimentis();

}



/**

 * 基本方法,將水煮沸

 * <pre>

 * 因為所有煮水的操作是相同的,子類不需要再重寫該方法,因此宣告為私有,以減小子類的複雜度

 * </pre>

 * @author dengjunzhen

 * @date 2016年2月2日上午10:40:07

 */

private void boilWater() {

    // 簡單的實現一下

    System.out.println("將水煮沸");

}

/**

 * 泡製飲料

 * <pre>

 * 泡製飲料的過程是不一樣的,因此在父類中只宣告,由子類來實現

 * </pre>

 * @author dengjunzhen

 * @date 2016年2月2日上午10:49:41

 */

protected abstract void brew();



/**

 * 將飲料倒入杯中

 * @author dengjunzhen

 * @date 2016年2月2日上午10:44:17

 */

private void pourInCunp() {

    // 簡單的實現

    System.out.println("將飲料倒入杯中");

}

/**

 * 加入調料

 * @author dengjunzhen

 * @date 2016年2月2日上午10:52:06

 */

protected abstract void addCondimentis() ;

}

第二步,定義一個泡咖啡的實現類:

package com.dengjz.tmp;

/**

  • 泡製咖啡

  • @author dengjunzhen

  • @date 2016年2月2日上午10:56:59

*/

public class Coffee extends RefreshBeverage{

@Override

protected void brew() {

    System.out.println("用沸水沖泡咖啡");

}

@Override

protected void addCondimentis() {

    System.out.println("加入糖和牛奶");

}

}

第三部,定義泡茶的實現類:

package com.dengjz.tmp;

/**

  • 泡茶

  • @author dengjunzhen

  • @date 2016年2月2日上午11:06:43

*/

public class Tea extends RefreshBeverage {

@Override

protected void brew() {

    System.out.println("用80度溫水泡茶5分鐘");

}

@Override

protected void addCondimentis() {

    System.out.println("向茶中加入調料");

}

}

好了,我們來測試一下:

package com.dengjz.tmp;

/**

  • @author dengjunzhen

  • @date 2016年2月2日上午11:00:53

*/

public class RefreshBeverageTest {

public static void main(String[] args) {

    System.out.println("泡製咖啡...");

    RefreshBeverage b1 = new Coffee();

    b1.prepareBeverageTemplate();

    System.out.println("咖啡好了!");

    

    System.out.println("==========================");

    

    System.out.println("泡茶...");

    RefreshBeverage b2 = new Tea();

    b2.prepareBeverageTemplate();

    System.out.println("茶泡好了!");

}

}

輸出:
[圖片上傳中。。。(1)]
好了,至此,模板方法的簡單實現已經基本完成了。

三、鉤子方法
在上面的例子中,我們可能會有疑問:在第四步中,如果我不想向飲料中加入調料呢?或者我想加入其它的調料(如敵敵畏)呢?

下面就來講一下鉤子方法。

先修改基類中的

prepareBeverageTemplate方法:

public final void prepareBeverageTemplate() {
// 步驟1:將水煮沸
boilWater();
// 步驟2:泡製進料
brew();
// 步驟3:將飲料倒入杯中
pourInCunp();
if(isWantsCondiments()){
// 步驟4:加入調料
addCondimentis();
}

}

向基類中新增一個方法:
/**
* 詢問是否加入調料
* <pre>
* 鉤子(Hook)方法,提供一個預設戶或空的實現,子類可以決定是否掛鉤以及如何掛鉤
* </pre>
* @author dengjunzhen
* @return
* @date 2016年2月2日上午11:35:58
*/
protected boolean isWantsCondiments(){
return true;
}
預設的情況下,會向飲料中新增調料的,如果我現在想泡一杯茶而不想新增調料呢?
很簡單,只要重寫父類的isWantsCondiments方法就可以了,這個步驟稱之為掛鉤:

/**     * 子類通過重寫的形式掛鉤鉤子函式     */    @Override    protected boolean isWantsCondiments() {        return false;    }      
好了,我們來測試一下:

[圖片上傳中。。。(2)]

通過結果可以看到,泡咖啡時沒有掛鉤,仍然會向咖啡中新增調料,而茶掛鉤了,告訴父類,“我不要調料了”,因此,泡茶時並沒有執行新增調料的動作。

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

優點:

    1.封裝性好;

    2.複用性好;

    3.遮蔽細節;

    4.便於維護;

缺點:

    java只支援單繼承,因此若採用模板方法,子類將不能再繼承其他類

相關文章