設計模式8——模板方法模式

如果餓了就喫,困了就睡,渴了就喝,人生就太無趣了


1 定義

1.1 概念

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

1.2 類圖

如圖1,抽象類AbstractClass中的templateMethod方法定義了算法框架,將算法分步驟實現,每一步的定義爲抽象方法primitiveOperation1()primitiveOperation2()。由實現的AbstractClass的實例類進行重寫該靜態方法。

在這裏插入圖片描述

2 例子(咖啡店)

如圖2:做咖啡的步驟,分爲四步:燒水boilWater(),沸水衝咖啡boilCoffeeGrinds(),倒進杯子pourInCup(),加糖加奶addSugarAndMilk()

在這裏插入圖片描述

如圖3:做茶的步驟,也分爲四步:燒水boilWater(),沸水泡茶包steepTeaBag(),倒進杯子pourInCup(),加檸檬addLemon()

在這裏插入圖片描述

可以發現這兩種做法有兩個相同的步驟:燒水boilWater()和倒進杯子pourInCup()

2.1 改進設計

如圖4:設計一個抽象類CoffeineBeverage,將剛剛發現的共同點抽象出來在該類中實現,將prepareRecipe()方法作爲抽象方法。CoffeeTea繼承CoffeinBeverage,並重寫prepareRecipe(),將不同的步驟進行實現。

在這裏插入圖片描述

2.2 再次改進

在看咖啡和茶的不同的步驟:

  1. 沸水衝咖啡boilCoffeeGrinds()和沸水泡茶包steepTeaBag(),可以看出兩個步驟的其實也是相同的動作,只是將不同的料放入沸水中。
  2. 加糖加奶addSugarAndMilk()和加檸檬addLemon()。這兩個步驟也是相同的動作,只不過是加的料不同。

再次進行改進,如圖5:個抽象類CoffeineBeverage將不相同的兩個步驟進行抽象化,生成兩個抽象方法brew()addCondiments(),由CoffeeTee來實現這個抽象放方法,實現prepareRecipe()方法,做步驟進行固定化,將prepareRecipe()方法使用final關鍵字修飾,此時就應用了模板方法模式。

在這裏插入圖片描述
代碼

public abstract class CaffeineBeverage {

    final void prepareRecipe() {
        boilWater();
        brew();
        pourInCup();
        addCondiments();
    }

    abstract void brew();

    abstract void addCondiments();

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

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

public class Coffee extends CaffeineBeverage {
    public void brew() {
        System.out.println("Dripping Coffee through filter");
    }
    public void addCondiments() {
        System.out.println("Adding Sugar and Milk");
    }
}

public class Tea extends CaffeineBeverage {
    public void brew() {
        System.out.println("Steeping the tea");
    }
    public void addCondiments() {
        System.out.println("Adding Lemon");
    }
}

public class BeverageTestDrive {
    public static void main(String[] args) {

        Tea tea = new Tea();
        Coffee coffee = new Coffee();

        System.out.println("\nMaking tea...");
        tea.prepareRecipe();

        System.out.println("\nMaking coffee...");
        coffee.prepareRecipe();

    }
}

2.3 人性化改進

有些人喝咖啡不需要糖和奶,喝茶不要檸檬,此時算法就需要進行改動,更加人性化。此時在抽象類中加入鉤子方法,其實現只有空和默認方法。鉤子方法對算法的不同點進行掛鉤,對於要不要掛鉤,由子類決定。(直接看類圖吧,說不清啦)
如圖6:在抽象類CoffeineBeverage中添加customerWantsCondiments()方法,此方法就是鉤子方法,直接返回值,不做任何操作,但此方法卻在perpareRecipe()起到決定是否至執行addCondiments()方法。此時子類就可以重寫該方法,讓顧客決定是否加料。
在這裏插入圖片描述

代碼

public abstract class CaffeineBeverageWithHook {

    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;
    }
}

public class CoffeeWithHook extends CaffeineBeverageWithHook {

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

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

    public boolean customerWantsCondiments() {

        String answer = getUserInput();

        if (answer.toLowerCase().startsWith("y")) {
            return true;
        } else {
            return false;
        }
    }

    private String getUserInput() {
        String answer = null;

        System.out.print("Would you like milk and sugar with your coffee (y/n)? ");

        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        try {
            answer = in.readLine();
        } catch (IOException ioe) {
            System.err.println("IO error trying to read your answer");
        }
        if (answer == null) {
            return "no";
        }
        return answer;
    }
}

public class TeaWithHook extends CaffeineBeverageWithHook {

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

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

    public boolean customerWantsCondiments() {

        String answer = getUserInput();

        if (answer.toLowerCase().startsWith("y")) {
            return true;
        } else {
            return false;
        }
    }

    private String getUserInput() {
        // get the user's response
        String answer = null;

        System.out.print("Would you like lemon with your tea (y/n)? ");

        BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
        try {
            answer = in.readLine();
        } catch (IOException ioe) {
            System.err.println("IO error trying to read your answer");
        }
        if (answer == null) {
            return "no";
        }
        return answer;
    }
}

public class BeverageTestDrive {
    public static void main(String[] args) {
        TeaWithHook teaHook = new TeaWithHook();
        CoffeeWithHook coffeeHook = new CoffeeWithHook();

        System.out.println("\nMaking tea...");
        teaHook.prepareRecipe();

        System.out.println("\nMaking coffee...");
        coffeeHook.prepareRecipe();
    }
}

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