設計模式 - D8 - 模板方法模式
設計模式 - 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就是一個高層元件,它控制了演算法的執行,只有在需要子類實現某個方法時,才會呼叫子類