模板方法模式--我們一起下餃子

餃子製作的過程

小帥在外奔波多年,厭倦了程序員的生活,終於決定回老家安定下來,開了個餃子館,天天下餃子。

小帥跟一個大廚苦練餃子製作,自己總結了一下餃子製作的過程:

擀麪皮
包餡
水煮
出鍋
加基本調料
加其他調料(顧客自行添加)

小帥就以這個流程爲模板,在店裏做各種各樣的餃子,恩,生意還挺好的。

普通方法做餃子

小帥發揮程序員的特長,把做餃子的經過用代碼寫了下來,其實很簡單:

製作韭菜餡餃子

/**
 * 韭菜餡餃子
 */
public class LeekDumpling {
    /**
     * 做餃子
     */
    public void makeDumpling() {
        // 擀麪皮
        ganMianPi();
        // 包餡
        baoXian();
        // 水煮
        shuiZhu();
        // 出鍋
        chuGuo();
        // 加基本調料
        jiaJiBenTiaoLiao();
        // 加其他調料
        jiaQiTaTiaoLiao();
    }

    // 擀麪皮
    public void ganMianPi() {
        System.out.println("擀麪皮");
    }

    // 包餡
    public void baoXian() {
        System.out.println("包韭菜餡");
    }

    // 水煮
    public void shuiZhu() {
        System.out.println("水煮8分鐘");
    }

    // 出鍋
    public void chuGuo() {
        System.out.println("出鍋");
    }

    // 加基本調料
    public void jiaJiBenTiaoLiao() {
        System.out.println("加醋");
    }

    // 加其他調料
    public void jiaQiTaTiaoLiao() {
    }
}

製作豬肉餡餃子

/**
 * 豬肉餡餃子
 */
public class PorkDumpling {
    /**
     * 做餃子
     */
    public void makeDumpling() {
        // 擀麪皮
        ganMianPi();
        // 包餡
        baoXian();
        // 水煮
        shuiZhu();
        // 出鍋
        chuGuo();
        // 加基本調料
        jiaJiBenTiaoLiao();
        // 加其他調料
        jiaQiTaTiaoLiao();
    }

    // 擀麪皮
    public void ganMianPi() {
        System.out.println("擀麪皮");
    }

    // 包餡
    public void baoXian() {
        System.out.println("包豬肉餡");
    }

    // 水煮
    public void shuiZhu() {
        System.out.println("水煮10分鐘");
    }

    // 出鍋
    public void chuGuo() {
        System.out.println("出鍋");
    }

    // 加基本調料
    public void jiaJiBenTiaoLiao() {
        System.out.println("加醋");
    }

    // 加其他調料
    public void jiaQiTaTiaoLiao() {
        System.out.println("加重辣");
    }
}

在這裏插入圖片描述

在這裏插入圖片描述
小帥的店裏不僅有韭菜餡餃子,豬肉餡餃子還有牛肉餡,蘑菇餡,海鮮餡餃子等等十幾種呢,各種餃子的製作過程其實都是大同小異,主要是包的餡不一樣,然後就水煮的時間不一樣。

如果每種餃子都各自實現一次所有的代碼,會出現大量的重複代碼,該怎麼改進呢?

模板方法做餃子

其實每種餃子的製作都是按固定的流程製作的,這種情況就非常適合使用模板方法模式。

模板方法模式定義如下:模板方法模式在一個方法中定義一個算法骨架,並將某些步驟推遲到子類中實現。模板方法模式可以讓子類在不改變算法整體結構的情況下,重新定義算法中的某些步驟。

類圖如下:
在這裏插入圖片描述

按照模板方法模式改造一下:

餃子抽象類

public abstract class Dumpling {

    /**
     * 做餃子
     */
    public final void makeDumpling() {
        // 擀麪皮
        ganMianPi();
        // 包餡
        baoXian();
        // 水煮
        shuiZhu();
        // 出鍋
        chuGuo();
        // 加基本調料
        jiaJiBenTiaoLiao();
        // 鉤子
        hook();
    }

    /**
     * 擀麪皮
     */
    public void ganMianPi() {
        System.out.println("擀麪皮");
    }

    /**
     * 包餡(抽象方法需要在子類中實現)
     */
    public abstract void baoXian();

    /**
     * 水煮(抽象方法需要在子類中實現)
     */
    public abstract void shuiZhu();

    /**
     * 出鍋
     */
    public void chuGuo() {
        System.out.println("出鍋");
    }

    /**
     * 加基本調料
     */
    public void jiaJiBenTiaoLiao() {
        System.out.println("加醋");
    }

    /**
     * 鉤子(自定義擴展方法,默認實現爲空)
     */
    public void hook() {
    }

}

韭菜餡餃子

/**
 * 韭菜餡餃子
 */
public class LeekDumpling extends Dumpling{

    /**
     * 包餡
     */
    @Override
    public void baoXian() {
        System.out.println("包韭菜餡");
    }

    /**
     * 水煮
     */
    @Override
    public void shuiZhu() {
        System.out.println("水煮8分鐘");
    }
}

豬肉餡餃子

/**
 * 豬肉餡餃子
 */
public class PorkDumpling extends Dumpling{

    /**
     * 包餡
     */
    @Override
    public void baoXian() {
        System.out.println("包豬肉餡");
    }

    /**
     * 水煮
     */
    @Override
    public void shuiZhu() {
        System.out.println("水煮10分鐘");
    }


    /**
     * 覆蓋hook()方法,加其他調料
     */
    @Override
    public void hook() {
        System.out.println("加重辣");
    }
}

製作餃子

public class MakeDumpling {

    public static void main(String[] args) {
        // 製作韭菜餡餃子
        System.out.println("製作韭菜餡餃子");
        LeekDumpling leekDumpling = new LeekDumpling();
        leekDumpling.makeDumpling();

        System.out.println();
        // 製作豬肉餡餃子
        System.out.println("製作豬肉餡餃子");
        PorkDumpling porkDumpling = new PorkDumpling();
        porkDumpling.makeDumpling();
    }
}

輸出結果

製作韭菜餡餃子
擀麪皮
包韭菜餡
水煮8分鐘
出鍋
加醋

製作豬肉餡餃子
擀麪皮
包豬肉餡
水煮10分鐘
出鍋
加醋
加重辣

在這裏插入圖片描述

餃子的固定制作過程,擀麪皮【ganMianPi()】,出鍋【chuGuo()】, 加基本調料【jiaJiBenTiaoLiao()】每種餃子都是一樣的,所以直接在父類中實現,實現了代碼的複用。

包餡【baoXian()】,水煮【shuiZhu()】每種餃子都不一樣,定義爲抽象方法,具體的操作推遲到子類中實現。

鉤子【hook()】方法比較有意思,在父類中默認實現爲空,如果子類有什麼特殊的操作,父類中沒有定義的,那麼子類就可以覆蓋hook(),實現自己擴展的業務邏輯。

比如加辣椒,不是每個客人都喜歡喫辣的,那麼需要加辣椒的顧客自己覆蓋hook()方法,加上辣椒就行了,不需要加辣椒的顧客就不用重寫hook()方法了。

總結

在模板模式經典的實現中,模板方法定義爲 final,可以避免被子類重寫。
需要子類重寫的方法定義爲 abstract,可以強迫子類去實現。
如果子類需要擴展,可以預留一個hook()的空實現方法,讓需要的子類去重寫。

該模式應用了好萊塢原則:別調用(打電話給)我們,我們會調用(打電話給)你

換句話說就是:子類你別調用我們(父類),我們(父類)會調用你。

模板模式有兩大作用:複用和擴展

複用指的是,所有的子類可以複用父類中提供的模板方法的代碼。

擴展指的是,框架通過模板模式提供功能擴展點,也就是鉤子hook(),讓框架用戶可以在不修改框架源碼的情況下,基於擴展點定製化框架的功能。

代碼鏈接
(歡迎關注公衆號:編程我也會)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章