no_mkd
一.什麼是模版方法模式?
首先,模版方法模式是用來封裝演算法骨架的,也就是演算法流程
既然被稱為模版,那麼它肯定允許擴展類套用這個模版,為了應對變化,那麼它也一定允許擴展類做一些改變
事實就是這樣,模版方法模式封裝了演算法流程,但允許由子類負責實現某些步驟細節
二.舉個例子
假設我們要開一家允許加盟的炸醬麵店,我們擁有獨家秘製的醬料配方,以及獨特的製作工藝,美味只此一家,地球上大多數人每天都吃我們的炸醬麵,所以我們有了大量的加盟者
為了盈利,我們當然不能公開製作工藝與醬料配方,但由於加盟者經營地域的差異,製作細節又存在差異,比如北京人喜歡吃細麵硬麵,陝西人喜歡吃寬麵軟麵,我們也不能為了保護配方而讓北京的加盟者繼續賣我們的寬麵
所以我們需要把製作工藝與保密配方保護起來,誰都不允許修改,同時把麵條的製作以及烹煮方式公開出去,允許加盟者修改。那麼,要如何實現這樣的需求?
沒錯,模版方法就是為此而生的
首先,我們需要建立模版類,把該保護的保護起來,把該公開的公開出去:
package TemplateMethodPattern;/**
-
@author ayqy
-
定義炸醬麵模版類 */ public abstract class Noodles { public void cook(){ //製作麵條 makeNoodles(); //製作醬料 makeSauce(); //烹煮麵條 boilNoodles(); //添加醬料 addSauce(); }
/**
- 把秘製醬料保護起來 */ private void makeSauce(){ System.out.println("做好一份獨家秘製醬料"); }
/**
- 把添加劑量保護起來 */ private void addSauce(){ System.out.println("添加適量的秘製醬料"); }
public abstract void makeNoodles(); public abstract void boilNoodles(); }
定義好了模版,90% 的工作就已經完成了,下面開始實現具體類
北京炸醬麵:
package TemplateMethodPattern;/**
-
@author ayqy
-
實現北京炸醬麵具體類 */ public class BJNoodles extends Noodles{
@Override public void makeNoodles() { System.out.println("做好一份有北京特色的手工麵"); }
@Override public void boilNoodles() { System.out.println("按北京特色煮麵法煮好麵條"); } }
陝西炸醬麵:
package TemplateMethodPattern;/**
-
@author ayqy
-
實現陝西炸醬麵具體類 */ public class SXNoodles extends Noodles{
@Override public void makeNoodles() { System.out.println("做好一份有陝西特色的手工麵"); }
@Override public void boilNoodles() { System.out.println("按陝西特色煮麵法煮好麵條"); } }
有了這些具體類,我們按照地域分配給加盟者就好了
三.效果示例
先實現一個測試類:
package TemplateMethodPattern;/**
-
@author ayqy
-
實現一個測試類 */ public class Test { public static void main(String[] args){ //創建北京炸醬麵對象 Noodles bjnoodles = new BJNoodles(); //創建陝西炸醬麵對象 Noodles sxnoodles = new SXNoodles();
System.out.println("北京炸醬麵製作工藝:"); bjnoodles.cook();//做一份北京炸醬麵 System.out.println("\n陝西炸醬麵製作工藝:"); sxnoodles.cook();//做一份陝西炸醬麵} }
運行結果:

四.多一點思考
上面的例子中,我們很輕鬆地實現了對演算法骨架的封裝,而且允許擴展類自定義某些步驟細節,似乎很輕鬆也很完美
那好,讓我們改改需求吧:
最近提倡綠色健康生活,大家都喜歡吃點蔬菜,我們傳統的炸醬麵不得不與時俱進,做好之後還要添點蔬菜。但問題是有的地方並不喜歡添加蔬菜,他們習慣了老字號炸醬麵的風味,堅決不要蔬菜。所以我們不能簡單地改變製作工藝,添加一道工序來加蔬菜。如果能在模版中定義一個可選的操作就再好不過了,讓加盟者自己選擇要不要加點兒蔬菜
於是,我們需要對之前的模版做一點點改動:
package TemplateMethodPattern;/**
-
@author ayqy
-
定義炸醬麵模版類 */ public abstract class Noodles { private boolean wantVegetables = false;//要不要蔬菜
public void setWantVegetables(boolean wantVegetables) { this.wantVegetables = wantVegetables; }
public void cook(){ //製作麵條 makeNoodles(); //製作醬料 makeSauce(); //烹煮麵條 boilNoodles();
//要不要添點兒蔬菜 if(wantVegetables) addVegetables(); //添加醬料 addSauce();}
/**
- 把秘製醬料保護起來 */ private void makeSauce(){ System.out.println("做好一份獨家秘製醬料"); }
/**
- 把添加劑量保護起來 */ private void addSauce(){ System.out.println("添加適量的秘製醬料"); }
/**
- 添加時令蔬菜 */ public void addVegetables(){ //空的實現 }
public abstract void makeNoodles(); public abstract void boilNoodles(); }
注意,為了添加可選的操作,我們做了這麼幾件事情:
- 定義標誌變量(選/不選)
- 提供標誌變量的 setter,供擴展類選擇
- 修改演算法骨架,加入新的可選步驟
- 為新的步驟提供一個空的實現(注意,這一步很重要,提供空的實現而不是定義一個抽象方法,避免了對現有擴展類的修改,這個在模版方法模式的術語中被稱為Hook 鉤子)
五.模版方法模式與策略模式
這兩個模式都是用來封裝演算法的,讓我們來對比一下:
| 策略模式 | 模版方法模式 | |
| 概念 | 封裝演算法步驟,允許子類選擇已有的策略(步驟細節) | 封裝演算法骨架(流程),允許由子類負責實現某些細節 |
| 實現方式 | 用組合來實現 | 用繼承來實現 |
| 目標 | 實現了演算法步驟的選擇 | 實現了演算法的流程控制 |
| 亮點 | 支持運行時動態改變步驟(策略) | 支持運行時動態改變演算法流程(用 hook 來實現) |
| 具體步驟 | 把易於變化的同類演算法細節(步驟)找出來,再定義一個演算法族(接口)把它們封裝起來 | 把演算法骨架抽象出來並封裝在基類(模版類)中 |
| 總結 | 封裝步驟 | 封裝流程 |
舉個例子:
假設現在有一個演算法,流程是 A->B->C->D,C 步驟的具體實現可能有 c1,c2,c3 三種不同方法
策略模式:
- 定義一個行為接口 C,定義 execute 方法
- 實現具體類 c1,c2,c3 擴展自接口 C(c1,c2,c3 三種行為供調用者選擇)
- 在基類中添加一個屬性,類型為接口 C,並提供 setter
- 擴展自基類的具體類將通過調用 setter 來動態改變行為
模版方法模式:
- 定義基類(模版類),在基類中定義演算法流程(把流程封裝起來)
- 在基類中把 C 步驟定義為抽象方法
- 擴展自基類的具體類將提供自己的實現(c1,c2,c3 或者其它)
暫無評論,快來發表你的看法吧