天天看點

設計之禅——模闆方法模式一、引言二、定義三、代碼實作四、總結

一、引言

模闆方法模式在我們平時開發中是非常常見,也是非常容易了解的,在平時不經意間就會使用到,是以了解其設計思想是非常有必要的。

二、定義

在《Head First設計模式》一書中是如下定義模闆方法模式的:

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

通俗一點說也就是,我們需要定義一個固定的算法步驟,而每個步驟則可以讓客戶通過繼承來實作個性化自定義,這樣也就遵循了對擴充開放,對修改關閉原則,極大程度的實作代碼複用以及保證代碼的擴充性。

同時模闆方法模式還需要遵循一個原則:好萊塢原則。那什麼是好萊塢原則呢?

别調用我們,我們會調用你。

這是高層元件(父類)對待低層元件的方式,簡單的說就高層元件可以調用低層元件,低層元件不允許直接調用高層元件。為什麼要這樣呢?想象一下,高層元件依賴低層元件,低層元件依賴于高層元件,而高層元件又依賴于側邊元件,側邊元件又依賴于低層元件……形成“依賴腐敗”,也就是環狀依賴,在這種情況下,想要輕易的了解系統的設計就非常的難了,是以良好的設計應該遵循好萊塢原則。

三、代碼實作

模闆方法模式對應生活中的執行個體也非常的多,這裡我以做飯炒菜來說明。炒菜都需要經曆洗菜、切菜、炒菜、出鍋等幾個過程,是以這是一個固定的過程,我将其抽象為炒菜前準備、炒菜中以及擺盤三個過程作為算法的骨架,放到doCooking方法中,并将該方法設定為final,保證其行為不會被改變;preCooking()和putPlate()假定都是一樣的,而具體要做什麼菜則是由子類來實作,代碼如下:

public abstract class Vegetables {
	
    public final void doCooking() {
        preCooking();
        cooking();
        putPlate();
    }

    private void preCooking() {
        System.out.println("Wash vegetables and light a fire!");
    }

    protected abstract void cooking();

    private void putPlate() {
        System.out.println("Put plate!");
    }

}

public class Potato extends Vegetables {

    @Override
    protected void cooking() {
        System.out.println("炸洋芋片!");
    }

}           

複制

代碼非常簡單,客戶點什麼菜,則執行個體化相應的子類,再調用父類的doCooking方法就能做出相應的菜。假如沒有模闆方法,那麼每一道菜都要去實作全部的流程,大量重複的工作将是一場災難,代碼看起來也會非常的臃腫,而通過模闆方法我們就将變化的部分解耦出來,大大的減少代碼量,實作代碼的複用。但是,我這裡隻有一個抽象的過程,如果父類中定義了多個抽象的過程呢?那其下所有子類也需要将所有的方法實作一遍,是以在抽象步驟的劃分時一定要掌握好度。

上面的實作看起來相當不錯了,但是它隻是一個最基本的實作,并不能滿足實際複雜的業務。當某個步驟并不是必須的時候該如何處理呢?比如,當客戶點了一道“涼拌黃瓜”,但有的客戶要求去皮,有的不用。思考一下在上面的例子中要如何做。我們隻需要在父類中加入一個“鈎子”方法來作為條件控制就行了,如下:

public abstract class Vegetables {

    public final void doCooking() {
        preCooking();
        if (isPeel()) {
            peel();
        }
        cooking();
        putPlate();
    }

    private void preCooking() {
        System.out.println("Wash vegetables and light a fire!");
    }

    private void peel() {
        System.out.println("Peel!");
    }

    protected abstract void cooking();

    private void putPlate() {
        System.out.println("Put plate!");
    }

	// 鈎子方法
    protected boolean isPeel() {
        return true;
    }

}           

複制

父類中增加一個peel步驟,該步驟由鈎子方法isPeel()來控制是否需要執行,父類一般預設控制需要或不需要,而真正的條件邏輯應由子類覆寫該方法來實作:

public class Cucumber extends Vegetables {

    @Override
    protected void cooking() {
        System.out.println("涼拌黃瓜!");
    }

    @Override
    protected boolean isPeel() {
        Scanner sc = new Scanner(System.in);
        String str = sc.next();

        if ("n".equals(str)) {
            return false;
        }
        return true;
    }
}           

複制

每次都詢問客戶是否需要“去皮”,當客戶輸入“n”時,不執行peel()方法,否則都會執行,其它菜品可以覆寫此方法也可以不覆寫,不覆寫預設也會去皮。也就使得步驟的執行與否交由子類來控制,更加的靈活。

四、總結

模闆方法模式實作很簡單,了解起來也不難,實際應用也非常廣泛,但它也不是完美無缺的,像上面所說的抽象步驟一旦劃分過多,實作起來也是相當的繁複,而且,每增加一個實作都需要添加一個類,也就使得系統越來越龐大。是以在實際設計過程中也需要好好考慮。

同時模闆方法在實際中常常會有許多的變種,像java api中的Arrays.sort()方法以及上次講的工廠方法其實都是模闆方法模式實作的,我們需要真正的了解每種模式的設計思想,多看優秀的代碼設計,才能應對萬般變化。最後貼上《Head First設計模式》中對于模闆方法模式總結的要點,共同進步!

  1. 模闆方法定義了算法的步驟,把這些步驟的實作延遲到子類;
  2. 為了防止子類改變模闆方法中的算法,可以将模闆方法定義為final;
  3. 鈎子是一種方法,它在抽象類中不做事,或隻做預設的事,子類可以選擇是否覆寫;
  4. 好萊塢原則告訴我們,将決策權放在高層元件中,以便決定如何以及何時調用低層元件;
  5. 政策模式和模闆方法都封裝算法,一個用組合,一個用繼承;
  6. 工廠方法是特殊的模闆方法。