一直以來我總是容易將工廠方法模式與模板方法模式混淆,它們兩個實在太像了.對於我來說造成這樣的錯覺,源於我對模板方法模式的不瞭解,這裏重點學習模板方法模式.
一、什麼是模板方法模式
模板方法模式在一個方法中定義一個算法的骨架,而將一些步驟延遲到子類中.模板方法使得子類可以在不改變算法結構的情況下,重新定義算法中的某些步驟.
模版方法模式的結構
模版方法模式由一個抽象類和一個(或一組)實現類通過繼承結構組成,抽象類中的方法分爲三種:
- 抽象方法:父類中只聲明但不加以實現,而是定義好規範,然後由它的子類去實現。
- 模版方法:由抽象類聲明並加以實現。一般來說,模版方法調用抽象方法來完成主要的邏輯功能,並且,模版方法大多會定義爲final類型,指明主要的邏輯功能在子類中不能被重寫。
- 鉤子方法:由抽象類聲明並加以實現。但是子類可以去擴展,子類可以通過擴展鉤子方法來影響模版方法的邏輯。
- 抽象類的任務是搭建邏輯的框架,通常由經驗豐富的人員編寫,因爲抽象類的好壞直接決定了程序是否穩定性。
實現類用來實現細節。抽象類中的模版方法正是通過實現類擴展的方法來完成業務邏輯。只要實現類中的擴展方法通過了單元測試,在模版方法正確的前提下,整體功能一般不會出現大的錯誤。
注意事項:爲防止惡意操作,一般模板方法都加上 final 關鍵詞。
二、如何使用模板方法模式
模板,是指作圖或設計方案的固定格式。模板將一個事物的結構規律予以固定化、標準化的成果,它體現的是結構形式的標準化 .模板方法就是提供一個算法框架,框架裏面的步驟有些是父類已經定好的,有些需要子類自己實現。比如:現在我們很多家庭都有了豆漿機,豆漿的營養價值不用我多說了。製作豆漿的工序簡單點來說就是選材—>添加配料—>浸泡—>放到豆漿機打碎,通過添加不同的配料,可以製作出不同口味的豆漿,但是選材、浸泡和放到豆漿機打碎這幾個步驟對於製作每種口味的豆漿都是一樣的。
1、創建抽象類
package templatemethod.pattern;
//豆漿類,抽象類
public abstract class SoyaMilk {
//這是模板方法,用final修飾,不允許子類覆蓋。模板方法定義了製作豆漿的程序
final void prepareRecipe(){
selectMaterial();
addCondiments();
soak();
beat();
}
//選材方法,選擇黃豆
void selectMaterial(){
System.out.println("第一步、選擇好了新鮮黃豆");
}
//可以添加不同的配料,在這裏設置爲抽象方法,子類必須實現
abstract void addCondiments();
//浸泡
void soak(){
System.out.println("第三步、黃豆和配料開始浸泡,大概需要5個小時");
}
//放到豆漿機打碎
void beat(){
System.out.println("第四步、黃豆的配料放到豆漿機打碎");
}
}
2、創建紅棗豆漿
package templatemethod.pattern;
//紅棗豆漿
public class ReddatesSoyaMilk extends SoyaMilk{
//實現父類的添加配料方法
@Override
void addCondiments() {
System.out.println("第二步、添加紅棗配料");
}
}
3、創建核桃豆漿
package templatemethod.pattern;
//核桃豆漿
public class NutSoyaMilk extends SoyaMilk{
//實現父類的添加配料方法
@Override
void addCondiments() {
System.out.println("第二步、添加核桃配料");
}
}
4、測試製作豆漿
package templatemethod.pattern;
public class SoyaMilkTest {
public static void main(String[] args){
//製作紅棗豆漿
System.out.println();
System.out.println("-----製作紅棗豆漿步驟-------");
SoyaMilk reddatesSoyaMilk = new ReddatesSoyaMilk();
reddatesSoyaMilk.prepareRecipe();
//製作核桃豆漿
System.out.println();
System.out.println("-----製作核桃豆漿步驟-------");
SoyaMilk nutSoyaMilk = new NutSoyaMilk();
nutSoyaMilk.prepareRecipe();
}
}
三、模板方法模式的優缺點
優點:
容易擴展。一般來說,抽象類中的模版方法是不易反生改變的部分,而抽象方法是容易反生變化的部分,因此通過增加實現類一般可以很容易實現功能的擴展,符合開閉原則。
便於維護。對於模版方法模式來說,正是由於他們的主要邏輯相同,才使用了模版方法,假如不使用模版方法,任由這些相同的代碼散亂的分佈在不同的類中,維護起來是非常不方便的。
比較靈活。因爲有鉤子方法,因此,子類的實現也可以影響父類中主邏輯的運行。但是,在靈活的同時,由於子類影響到了父類,違反了里氏替換原則,也會給程序帶來風險。這就對抽象類的設計有了更高的要求。
在多個子類擁有相同的方法,並且這些方法邏輯相同時,可以考慮使用模版方法模式。在程序的主框架相同,細節不同的場合下,也比較適合使用這種模式。
缺點:
每一個不同的實現都需要一個子類來實現,導致類的個數增加,使得系統更加龐大。
四、模板方法模式的使用場景
1、有多個子類共有的方法,且邏輯相同。
2、重要的、複雜的方法,可以考慮作爲模板方法。
五、模板方法中的鉤子方法
在模板方法模式的父類中,我們可以定義一個方法,它默認不做任何事,子類可以視情況要不要覆蓋它,該方法稱爲“鉤子”。我們還是用上面做豆漿的例子來講解。
1、創建有鉤子方法的父類
package templatemethod.pattern;
//豆漿類,抽象類
public abstract class SoyaMilkWithHook {
//這是模板方法,用final修飾,不允許子類覆蓋。模板方法定義了製作豆漿的程序
final void prepareRecipe(){
selectMaterial();
//判斷是否添加配料
if(customerWantsCondiments()){
addCondiments();
}
soak();
beat();
}
//選材方法,選擇黃豆
void selectMaterial(){
System.out.println("選擇好了新鮮黃豆");
}
//可以添加不同的配料,在這裏設置爲抽象方法,子類必須實現
abstract void addCondiments();
//浸泡
void soak(){
System.out.println("材料開始浸泡,大概需要5個小時");
}
//放到豆漿機打碎
void beat(){
System.out.println("材料放到豆漿機打碎");
}
//鉤子方法,是否添加配料
boolean customerWantsCondiments(){
return true;
}
}
2、創建純豆漿子類
package templatemethod.pattern;
//製作純豆漿,不添加任何配料
public class PureSoyaMilk extends SoyaMilkWithHook{
@Override
void addCondiments() {
}
//覆蓋鉤子方法,不添加配料
@Override
boolean customerWantsCondiments(){
return false;
}
}
3、測試製作純豆漿
package templatemethod.pattern;
public class PureSoyaMilkTest {
public static void main(String[] args){
//製作純豆漿
System.out.println();
System.out.println("-----製作純豆漿步驟-------");
SoyaMilkWithHook pureSoyaMilk = new PureSoyaMilk();
pureSoyaMilk.prepareRecipe();
}
}
鉤子方法的作用
1、讓子類實現算法中的可選部分。算法中的某些步驟是可選的,子類可以做出決定是否需要這些步驟。
2、如果鉤子對於子類的實現不重要時,子類可以對鉤子置之不理。
3、鉤子可以讓子類能夠有機會對模板方法中某些即將發生的(或剛剛發生的)步驟作出反應。可以在鉤子中實現我們對於某個步驟執行需要作出的動作,模板方法的某個步驟執行時,調用鉤子。
參考博客來源:https://blog.csdn.net/disiwei1012/article/details/53467251