1. 程式人生 > 其它 >設計模式 - D8 - 模板方法模式

設計模式 - D8 - 模板方法模式

技術標籤:設計模式java設計模式

設計模式 - D8 - 模板方法模式

抽象演算法

假設現在我們有一個Coffe類和一個Tea類,他們分別實現了各自的飲料衝調方法

public class Coffee {

    public void prepareRecipe() {
        boilWater();
        brewCoffeeGrinds();
        pourInCup();
        addSugar();
    }

    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 addSugar
() { System.out.println("Adding Sugar and Milks"); } }
public class Tea {

    void prepareRecipe() {
        boilWater();
        steepTeaBag();
        addLemon();
        pourInCup();
    }

    public void boilWater() {
        System.out.println("Boiling water")
; } public void steepTeaBag() { System.out.println("Stepping the tea"); } public void addLemon() { System.out.println("Adding Lemon"); } public void pourInCup() { System.out.println("Pouring into Cup"); } }

觀察類結構,可以發現兩種飲料都有共同的衝調流程(即preapareRecipe()方法)基本一致方法,其中boilWater()和pourInCup()是相同的,而剩餘的方法雖然具體實現不同,但其實是類似的;所以我們可以將preapareRecipe()這方法稍作改變並抽取出來放到一個抽象超類中,超類提供boilWater()、pourInCup()的實現,同時將剩餘兩個方法設為抽象,要求其子類實現,從而得出下面的程式碼

public abstract class CaffeineBeverage {

    // 抽取出相同的流程,並設為final,防止子類改變
    public final void prepareRecipe() {
        boilWater();
        brew();
        addCondiments();
        pourInCup();
    }

    public void boilWater() {
        System.out.println("Boiling water");
    }

    // 將原來的steepTeaBag和brewCoffeeGrinds抽取為brew方法,並要求子類實現
    public abstract void brew();

    // 將原來的addSugar和addLemon方法抽取為addCondiments,並要求子類實現
    public abstract void addCondiments();

    public void pourInCup() {
        System.out.println("Pouring into Cup");
    }

}
public class Tea extends CaffeineBeverage{

    public void brew() {
        System.out.println("Stepping the tea");
    }

    public void addCondiments() {
        System.out.println("Adding Lemon");
    }

}
public class Coffee extends CaffeineBeverage{

    public void brew() {
        System.out.println("Dripping Coffee through filter");
    }

    public void addCondiments() {
        System.out.println("Adding Sugar and Milks");
    }
}

測試

public class TemplateTest {
    public static void main(String[] args) {
        Coffee coffee = new Coffee();
        coffee.prepareRecipe();
        System.out.println();
        Tea tea = new Tea();
        tea.prepareRecipe();
    }
}

輸出
Boiling waterDripping Coffee through filter
Adding Sugar and Milks
Pouring into Cup

Boiling water
Stepping the tea
Adding Lemon
Pouring into Cup

模板方法模式

其實在上一個例子中,抽取出來的CaffeineBeverage抽象超類中的preapaerRecipe()就是一個模板方法,它作為一個演算法的模板,在其中將每一個步驟用一個方法代表。這些方法有的由超類提供並處理,有的被設為抽象方法,必須由子類實現並處理。也就是模板方法定義了一個演算法的步驟,並允許子類為一個或多個步驟提供實現。
這種模式的的好處是

  • 模板方法類主導一切,它擁有演算法並保護這個演算法
  • 對於子類來說,模板方法類可將程式碼複用最大化
  • 演算法只存在一個地方,容易修改
  • 提供了一個框架,可以讓其他相關物件作為子類插入進來,其只需要實現自己的方法即可
  • 專注在演算法本身,而由子類提供完整的實現

定義

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

在這裡插入圖片描述
可見,模板方法在實現演算法的過程中,用到了兩個原語操作(primitiveOperation),模板方法本身與這兩個操作的具體實現之間解耦

一般形式

// 宣告為抽象類,要求子類實現其操作
public abstract class AbstractClass {
	// 模板方法宣告為final,避免子類更改
	final void templateMethod()	{
    	primitiveOperation1();
      	primitiveOperation2();
      	concreteOperation();
      	hook();
    }
  	
  	// 原語操作是抽象方法,子類必須實現
   abstract void primitiveOperation1();
   abstract void primitiveOperation1();
	
   // 宣告為final的具體方法,可被子類使用,但不能修改
   final void concreteOperation() {
  		// 實現
  	}
  
    // 一個什麼都不做的具體方法,這種方法被稱為“鉤子”
  	void hook() {}
}

鉤子 - hook

鉤子是一種被生命在抽象類中的方法,但只有空或預設的實現,其存在是讓子類有能力對演算法的不同點進行掛鉤,而要不要掛鉤,由子類自行決定。如下例子,customerWantsCondiments()就是一個鉤子,它控制了模板方法中是否執行某部分的演算法

public abstract class CaffeineBeverageWithHook {
    
    public final void prepareRecipe() {
        boilWater();
        brew();
        // 條件語句使用鉤子判斷
        if(customerWantsCondiments()) {
            addCondiments();
        }
        addCondiments();
        pourInCup();
    }

    public void boilWater() {
        System.out.println("Boiling water");
    }
    
    public abstract void brew();
    
    public abstract void addCondiments();

    public void pourInCup() {
        System.out.println("Pouring into Cup");
    }
    
    // 一個鉤子,子類可以覆蓋這個方法,但不是必須的
    public boolean customerWantsCondiments() {
        return true;
    }
  
}

所以,當子類必須提供演算法中某個方法或步驟的實現時,該方法應該宣告為抽象的。相反,當演算法某個方法是可選的,就使用鉤子,子類可以選擇實現或不實現這個鉤子

好萊塢原則

好萊塢原則:別調用我們,我們會呼叫你

好萊塢原則可以防止“依賴腐敗”(高層元件依賴低層元件,而低層元件又依賴高層元件,而高層元件又依賴邊側元件,而邊側元件又依賴低層元件)
好萊塢原則之下,我們允許低層元件將自己掛鉤到系統上,但是高層元件會決定什麼時候和怎樣使用這些低層元件,即對於高層元件對待低層元件的方式是:別調用我,我會呼叫你們
在之前的CaffeineBeverage例子中,CaffeineBeverage就是一個高層元件,它控制了演算法的執行,只有在需要子類實現某個方法時,才會呼叫子類