《Head First 設計模式》:模板方法模式

驚卻一目發表於2020-08-30

正文

一、定義

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

要點:

  • 模板方法定義了一個演算法的步驟,每個步驟都被一個方法所代表,而這幾個方法的具體實現可由子類提供。
  • 模板方法可確保演算法的結構保持不變,同時由子類提供部分實現。

二、實現步驟

1、建立一個抽象類,並定義模板方法

模板方法一般宣告為 final,以免子類改變演算法的步驟。
抽象類中,可以宣告一些鉤子方法,子類視情況決定要不要覆蓋它們。鉤子的存在,可以讓子類有能力對演算法的不同點進行掛鉤,使得模板方法更具有彈性。

/**
 * 抽象類
 */
public abstract class AbstractClass {
    
    /**
     * 模板方法
     */
    public final void templateMethod() {
        // 公共步驟,由抽象類實現
        commonStep();
        // 依賴於子類的步驟,由子類實現
        step1();
        step2();
        step3();
        // 鉤子方法,由子類決定要不要覆蓋
        hook();
    }
    
    /**
     * 步驟1
     */
    public abstract void step1();
    
    /**
     * 步驟2
     */
    public abstract void step2();
    
    /**
     * 步驟3
     */
    public abstract void step3();
    
    /**
     * 公共步驟
     */
    private void commonStep() {
        System.out.println("I'm common step!");
    }
    
    /**
     * 鉤子方法
     */
    public void hook() {
        // 可以空實現,也可以提供預設實現
    }
}

2、建立具體子類,並提供演算法步驟的具體實現

(1)具體子類A

/**
 * 具體子類A
 */
public class ConcreteClassA extends AbstractClass{

    @Override
    public void step1() {
        System.out.println("I'm step A1!");
    }

    @Override
    public void step2() {
        System.out.println("I'm step A2!");
    }

    @Override
    public void step3() {
        System.out.println("I'm step A3!");
    }
    
    @Override
    public void hook() {
        System.out.println("I'm hook step A!");
    }
}

(2)具體子類B

/**
 * 具體子類B
 */
public class ConcreteClassB extends AbstractClass{

    @Override
    public void step1() {
        System.out.println("I'm step B1!");
    }

    @Override
    public void step2() {
        System.out.println("I'm step B2!");
    }

    @Override
    public void step3() {
        System.out.println("I'm step B3!");
    }
}

3、通過使用不同的子類,來確定具體的演算法步驟

public class Test {
    
    public static void main(String[] args) {
        AbstractClass classA = new ConcreteClassA();
        classA.templateMethod();
        System.out.println();
        AbstractClass classB = new ConcreteClassB();
        classB.templateMethod();
    }
}

三、舉個例子

1、背景

《星巴茲咖啡師傅訓練手冊》規定了,準備星巴茲飲料時,必須精確地遵循下面的沖泡法:

星巴茲咖啡沖泡法:

  1. 把水煮沸
  2. 用沸水沖泡咖啡
  3. 把咖啡倒進杯子
  4. 加糖和牛奶

星巴茲茶沖泡法:

  1. 把水煮沸
  2. 用沸水浸泡茶葉
  3. 把茶倒進杯子
  4. 加檸檬

現在,讓我們扮演“程式碼師傅”,寫一些程式碼來建立咖啡和茶。

2、實現

(1)建立咖啡因飲料抽象類,並定義沖泡步驟的方法

/**
 * 咖啡因飲料抽象類
 */
public abstract class CaffeineBeverage {
    
    /**
     * 準備沖泡法(模板方法)
     */
    final void prepareRecipe() {
        boilWater();
        brew();
        pourInCup();
        if (customerWantsCondiments()) {
            addCondiments();
        }
    }
    
    /**
     * 沖泡(依賴於子類的步驟)
     */
    abstract void brew();
    
    /**
     * 新增調料(依賴於子類的步驟)
     */
    abstract void addCondiments();
    
    /**
     * 燒水(公共步驟)
     */
    void boilWater() {
        System.out.println("Boiling water");
    }
    
    /**
     * 把飲料倒進杯子(公共步驟)
     */
    void pourInCup() {
        System.out.println("Pouring into cup");
    }
    
    /**
     * 顧客是否想要新增調料(鉤子方法)
     */
    boolean customerWantsCondiments() {
        return true;
    }
}

(2)建立具體的飲料,並提供具體沖泡步驟的實現

/**
 * 咖啡
 */
public class Caffee extends CaffeineBeverage{

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

    @Override
    void addCondiments() {
        System.out.println("Adding Sugar and Milk");
    }
    
    @Override
    boolean customerWantsCondiments() {
        String answer = askCustomer();
        if (answer.toLowerCase().startsWith("y")) {
            return true;
        } else {
            return false;
        }
    }
    
    /**
     * 詢問顧客
     */
    private String askCustomer() {
        System.out.print("Would you like milk and suger with your coffee (y/n)?");
        String answer = null;
        Scanner scanner = new Scanner(System.in);
        if (scanner.hasNext()) {
            answer = scanner.nextLine();
        }
        scanner.close();
        if (answer == null) {
            return "no";
        }
        return answer;
    }
}
/**
 * 茶
 */
public class Tea extends CaffeineBeverage{

    @Override
    void brew() {
        System.out.println("Steeping the tea");
    }

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

(3)沖泡飲料

public class Test {
    
    public static void main(String[] args) {
        // 茶
        System.out.println("\nMaking tea...");
        CaffeineBeverage tea = new Tea();
        tea.prepareRecipe();
        // 咖啡
        System.out.println("\nMaking coffee...");
        CaffeineBeverage caffee = new Caffee();
        caffee.prepareRecipe();
    }
}

相關文章