模板模式在諸多模式中算是比較簡單的一個,即便一個新手,也能很快的上手和使用。雖然簡單,但合理的在項目中使用帶來的好處可不小。軟件設計中有個很重要的原則:DRY(Don't Repeat Yourself),重複有很多種,比如代碼重複,邏輯重複,而模板模式的最拿手的就是消除邏輯重複。
我們先看一下模板模式的定義:在一個方法中定義一個算法的骨架,而將一些步驟延遲到子類中去實現。模板方法使得子類可以在不改變算法結構的情況下,重新定義算法中的某些步驟。只要你學習過任何一門面嚮對象語言,那麼理解上面的定義肯定不是問題。
來看看模式的UML類圖:
AbstractClass是一個抽象類,其中templateMethod是一個final方法,封裝了具體的算法實現步驟、過程,其內部調用了其它方法。abstractMethod1與abstractMethod2是抽象方法,其具體實現由具體的子類決定,這樣子類就可以在不改變算法結構的情況下,重新定義算法中的某些步驟。值得一提的是hook方法,它是一個鉤子方法,在父類中會提供一個默認的實現,子類可以有選擇的決定是否要覆蓋鉤子方法。簡單而言,鉤子讓子類有機會修改算法中的某些步驟,是否使用鉤子完全取決於業務。
我們來看一個例子。
飲料 | 牛奶 | 咖啡 |
1. 把杯子洗乾淨。 2. 把水煮沸。 3. 把飲料放入杯子。 4. 把開水倒入杯中。 5. 加調料。 6. 攪拌。 |
1. 把杯子洗乾淨。 2. 把水煮沸。 3. 把奶粉放入杯子。 4. 把開水倒入杯中。 5. 加糖。 6. 攪拌。 |
1. 把杯子洗乾淨。 2. 把水煮沸。 3. 把咖啡放入杯子。 4. 把開水倒入杯中。 5. 加糖、伴侶。 6. 攪拌。 |
飲料(Beverage是一個抽象類,make方法封裝了飲料的製作流程,但具體制作什麼飲料,加不加調料、加什麼調料由給子類去決定,addCondiment方法是一個鉤子,默認什麼都不做。
public abstract class Beverage {
public final void make() {
System.out.println("把杯子洗乾淨。");
System.out.println("把水煮沸。");
this.addBeverage();
System.out.println("把水倒入杯中。");
this.addCondiment();
System.out.println("攪拌。");
}
protected abstract void addBeverage();
protected void addCondiment() {
//默認什麼調料都不加。
}
}
牛奶(Milk是一個實現類,僅實現addBeverage方法;Coffee也是一個實現類,實現了addBeverage,並且覆蓋了addCondiment方法(不加糖咖啡太苦啦)。
public class Milk extends Beverage {
@Override
protected void addBeverage() {
System.out.println("把奶粉倒入杯口。");
}
}
public class Coffee extends Beverage {
@Override
protected void addBeverage() {
System.out.println("把咖啡倒入杯口。");
}
@Override
protected void addCondiment() {
System.out.println("加糖和咖啡伴侶。");
}
}
運行結果:
public static void main(String[] args) {
Beverage milk = new Milk();
Beverage coffee = new Coffee();
System.out.println("做牛奶......");
milk.make();
System.out.println("\n做咖啡......");
coffee.make();
}
做牛奶......
把杯子洗乾淨。
把水煮沸。
把奶粉倒入杯口。
把水倒入杯中。
攪拌。
做咖啡......
把杯子洗乾淨。
把水煮沸。
把咖啡倒入杯口。
把水倒入杯中。
加糖和咖啡伴侶。
攪拌。
JDK中有哪些使用模板模式的部分?
像Collections.sort、Arrays.sort等方法,都需要一個實現Comparator接口的對象來決定如何比較對象(實現compare方法);
又如File.listFiles方法需要一個實現FileFilter接口的對象來決定如何過濾文件(通過實現accept方法),在JDK中類似的場景還有很多,這裏就不一一例舉了。