跳到主要內容
黯羽輕揚每天積累一點點

設計模式之模版方法模式(Template Method Pattern)

免費2015-03-07#Design_Pattern#模版方法模式#Template Method Pattern

模版方法模式,用來封裝演算法骨架(流程),某些步驟由子類實現。此外還有另一個模式:策略模式,二者都是用來封裝演算法的,但側重點不同,本文展開詳細討論。

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(); }

注意,為了添加可選的操作,我們做了這麼幾件事情:

  1. 定義標誌變量(選/不選)
  2. 提供標誌變量的 setter,供擴展類選擇
  3. 修改演算法骨架,加入新的可選步驟
  4. 為新的步驟提供一個空的實現(注意,這一步很重要,提供空的實現而不是定義一個抽象方法,避免了對現有擴展類的修改,這個在模版方法模式的術語中被稱為Hook 鉤子

五.模版方法模式與策略模式

這兩個模式都是用來封裝演算法的,讓我們來對比一下:

 策略模式模版方法模式
概念封裝演算法步驟,允許子類選擇已有的策略(步驟細節)封裝演算法骨架(流程),允許由子類負責實現某些細節
實現方式用組合來實現用繼承來實現
目標實現了演算法步驟的選擇實現了演算法的流程控制
亮點支持運行時動態改變步驟(策略)支持運行時動態改變演算法流程(用 hook 來實現)
具體步驟把易於變化的同類演算法細節(步驟)找出來,再定義一個演算法族(接口)把它們封裝起來把演算法骨架抽象出來並封裝在基類(模版類)中
總結封裝步驟封裝流程

舉個例子:

假設現在有一個演算法,流程是 A->B->C->D,C 步驟的具體實現可能有 c1,c2,c3 三種不同方法

策略模式:

  1. 定義一個行為接口 C,定義 execute 方法
  2. 實現具體類 c1,c2,c3 擴展自接口 C(c1,c2,c3 三種行為供調用者選擇)
  3. 在基類中添加一個屬性,類型為接口 C,並提供 setter
  4. 擴展自基類的具體類將通過調用 setter 來動態改變行為

模版方法模式:

  1. 定義基類(模版類),在基類中定義演算法流程(把流程封裝起來)
  2. 在基類中把 C 步驟定義為抽象方法
  3. 擴展自基類的具體類將提供自己的實現(c1,c2,c3 或者其它)

評論

暫無評論,快來發表你的看法吧

提交評論