如果餓了就喫,困了就睡,渴了就喝,人生就太無趣了
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()
方法作爲抽象方法。Coffee
和Tea
繼承CoffeinBeverage
,並重寫prepareRecipe()
,將不同的步驟進行實現。
2.2 再次改進
在看咖啡和茶的不同的步驟:
- 沸水衝咖啡
boilCoffeeGrinds()
和沸水泡茶包steepTeaBag()
,可以看出兩個步驟的其實也是相同的動作,只是將不同的料放入沸水中。 - 加糖加奶
addSugarAndMilk()
和加檸檬addLemon()
。這兩個步驟也是相同的動作,只不過是加的料不同。
再次進行改進,如圖5:個抽象類CoffeineBeverage
將不相同的兩個步驟進行抽象化,生成兩個抽象方法brew()
和addCondiments()
,由Coffee
和Tee
來實現這個抽象放方法,實現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();
}
}