設計模式【13】– 模板模式怎麼弄?

設計模式

開局還是那種圖,各位客官往下看…

張無忌學太極拳,忘記了所有招式,打倒了”玄冥二老”,所謂”心中無招”。設計模式可謂招數,如果先學通了各種模式,又忘掉了所有模式而隨心所欲,可謂OO之最高境界。

模板模式是什麼?

模板模式,同樣是一種行為型模式,也就是關於對象做什麼或者怎麼做的設計模式。模板模式的本質需要定義操作中的演算法的框架,但是有一些步驟,又不需要具體的實現,而是不同的子類各自實現。子類不能修改流程框架,但是部分的步驟可以做訂製化的實現。

主要要解決一個問題:一些通用的方法,但是每一個子類卻都重新寫,冗餘。

比如說,做菜的步驟一般是:洗鍋 --> 炒菜 --> 洗碗 ,不同的菜,只是炒菜這一個步驟具體細節是不同的,但是其他步驟確實幾乎一模一樣的,這樣其實整體框架,以及重複的步驟,我們可以抽象到模板中,而不同的細節方法可以開放給每一種菜(具體實現)去訂製。

又比如造房子的時候,很多地方的建造都是一樣的:地基,牆壁,水管等等,但是不同的房子裡面的內部的設計又有所不同。

不使用模板模式

就挑個簡單的例子「炒菜」,如果不使用模板模式的話,糖醋鯉魚:


public class SweetAndSourCarp {

    public void cookFood(){
        washPan();
        cook();
        eat();
        washDishes();
        System.out.println("");
    }

    private void washPan(){
        System.out.print("洗鍋 --> ");
    }

    private void cook(){
        System.out.print("煮糖醋鯉魚 --> ");
    }

    private void eat(){
        System.out.print("吃飯 --> ");
    }

    private void washDishes(){
        System.out.print("洗碗 --> ");
    }
}

再弄一個農家小炒肉,需要寫很多相同的方法:

public class ShreddedPorkWithVegetables {

    public void cookFood(){
        washPan();
        cook();
        eat();
        washDishes();
        System.out.println("");
    }

    private void washPan(){
        System.out.print("洗鍋 --> ");
    }

    private void cook(){
        System.out.print("炒農家小炒肉 --> ");
    }

    private void eat(){
        System.out.print("吃飯 --> ");
    }

    private void washDishes(){
        System.out.print("洗碗 --> ");
    }
}

測試類如下:

public class Test {
    public static void main(String[] args) {
        SweetAndSourCarp sweetAndSourCarp = new SweetAndSourCarp();
        sweetAndSourCarp.cookFood();

        ShreddedPorkWithVegetables shreddedPorkWithVegetables = new ShreddedPorkWithVegetables();
        shreddedPorkWithVegetables.cookFood();
    }
}

測試結果:

洗鍋 --> 煮糖醋鯉魚 --> 吃飯 --> 洗碗 --> 
洗鍋 --> 炒農家小炒肉 --> 吃飯 --> 洗碗 --> 

可以看到,整體流程是一樣的,有些步驟一樣,有些步驟不一樣,但是不使用模板模式,需要每個類都重寫一遍方法,即使是通用方法,整個流程都需要自己寫一遍。

使用模板模式優化

如果使用模板模式,那麼我們會抽象出一個抽象類,定義整體的流程,已經固定的步驟,開放需要訂製的方法,讓具體的實現類按照自己的需求來訂製。

定義的抽象類:

public abstract class CookFood {
    public final void cookFood() {
        washPan();
        cook();
        eat();
        washDishes();
        System.out.println("");
    }

    private final void washPan() {
        System.out.print("洗鍋 --> ");
    }

    public abstract void cook();

    private final void eat() {
        System.out.print("吃飯 --> ");
    }

    private final void washDishes() {
        System.out.print("洗碗 --> ");
    }
}

具體的實現類糖醋鯉魚:

public class SweetAndSourCarp extends CookFood {
    @Override
    public void cook() {
        System.out.print("煮糖醋鯉魚 --> ");
    }
}

農家小炒肉:

public class ShreddedPorkWithVegetables extends CookFood {
    @Override
    public void cook() {
        System.out.print("炒農家小炒肉 --> ");
    }
}

測試類與前面的一樣,測試結果也一樣,這裡不再重複。

上面的方法中,其實我們只開放了cook()方法,這就是鉤子方法

在模板方法模式的父類中,我們可以定義一個方法,它默認不做任何事,子類可以視情況要不要覆蓋它,該方法稱為 」鉤子方法」

鉤子方法是開放的,可以由子類隨意覆蓋,但是像上面的其他方法,我們不希望子類重寫或者覆蓋它,就可以用 final 關鍵字,防止子類重寫模板方法。

模板模式的應用

其實在 JDKThread 實現中,就是使用了模板模式,我們知道創建執行緒有兩個方式:

  • 創建 Thread
  • 實現 runnable 介面

我們實現的一般是 run() 方法, 但是調用的卻是 start() 方法來啟動執行緒,這個原因就是 start() 方法裡面幫我們調用了run() 方法, run()方法是開發的方法,我們可以覆蓋重寫它。

Start0()是一個native方法,是由 c 語言去實現的,在調用的時候,真正調用了我們的 run() 方法,如果需要跟蹤這個方法需要到 HotSpot底層去。這裡介紹的目的是讓大家了解,它同樣是使用了模板模式。

    private native void start0();

了解 native 關鍵字可以參考://aphysia.cn/archives/native

模板模式的優缺點

模板模式的優點:

  • 1、封裝固定的部分,拓展需要訂製修改的部分,符合開閉原則。
  • 2、公共的程式碼在父類中,容易維護。
  • 3、整個流程由父類把握,調整比較方便。

缺點:

  • 1、子類可能會很多,系統複雜度上升。
  • 2、子類只有一小部分實現,了解全部方法則需要在父類中閱讀,影響程式碼閱讀。

總結:程式碼該隱藏的複雜細節隱藏起來,開放訂製化部分,優雅!

設計模式系列:

【作者簡介】
秦懷,公眾號【秦懷雜貨店】作者,個人網站://aphysia.cn,技術之路不在一時,山高水長,縱使緩慢,馳而不息。

劍指Offer全部題解PDF

開源編程筆記