1. 程式人生 > >模板方法及策略設計模式實踐

模板方法及策略設計模式實踐

一、前言

       最近兩週工作比較忙,一直疏忽了寫部落格這件事。但是再忙也得堅持下去,雖然很難,但是自己定下的小目標含著淚也要把它做下去啊~~好了,廢話不多說直接進入正題吧。

       設計模式相信大家應該都有接觸過,並且在不經意間也使用過一些設計模式,使用設計模式最重要的一點是讓我們的程式碼看起來更清晰可讀,並且更具可擴充套件性,還可以達到程式碼最大程度複用的目標。所以今天我就來介紹一下最近在專案中用到的兩個設計模式,這兩個設計模同樣也是使用的比較多的兩個模式:模板方法模式以及策略模式。

二、模板方法模式

       模板方法模式是類的行為模式。準備一個抽象類,將部分邏輯以具體方法以及具體建構函式的形式實現,然後宣告一些抽象方法來迫使子類實現剩餘的邏輯。不同的子類可以以不同的方式實現這些抽象方法,從而對剩餘的邏輯有不同的實現。這就是模板方法模式的用意。

       模板方法模式是所有模式中最為常見的幾個模式之一,是基於繼承的程式碼複用的基本技術。模板方法模式需要開發抽象類和具體子類。抽象類給出一個演算法的輪廓和骨架,具體類則負責給出這個演算法的各個各個特定邏輯步驟。代表這些具體邏輯步驟的方法稱做基本方法(primitive method);而將這些基本方法彙總起來的方法叫做模板方法(template method),這個設計模式的名字就是從此而來。

      模板方法所代表的行為稱為頂級行為,其邏輯稱為頂級邏輯。模板方法模式的靜態結構圖如下所示:

這裡涉及到兩個角色:

  抽象模板(Abstract Template)角色有如下責任:

  ■  定義了一個或多個抽象操作,以便讓子類實現。這些抽象操作叫做基本操作,它們是一個頂級邏輯的組成步驟。

  ■  定義並實現了一個模板方法。這個模板方法一般是一個具體方法,它給出了一個頂級邏輯的骨架,而邏輯的組成步驟在相應的抽象操作中,推遲到子類實現。頂級邏輯也有可能呼叫一些具體方法。

  具體模板(Concrete Template)角色又如下責任:

  ■  實現父類所定義的一個或多個抽象方法,它們是一個頂級邏輯的組成步驟。

  ■  每一個抽象模板角色都可以有任意多個具體模板角色與之對應,而每一個具體模板角色都可以給出這些抽象方法(也就是頂級邏輯的組成步驟)的不同實現,從而使得頂級邏輯的實現各不相同。

      有句名言說的好:Talk is cheap. Show me the code…拿出程式碼才有說服力嘛!那麼我們就以炒菜這件事為例來實現一下模板方法模式吧。首先我們來梳理一下,如果要做一個菜需要做些什麼事項,以及這些事項的順序。俗話說,巧婦難為無米之炊。。。那最先肯定需要原材料,所以就需要去買菜;買到菜之後就是考驗動手能力的時候了,需要洗菜,切菜以及做菜了。通過我個人總結,這個世界上的所有菜都有個基本操作順序:買菜-->洗菜-->準備菜-->做菜-->出鍋,所有菜的做法都是大概就是這個抽象的流程。因此,我們可以構建一個抽象類來定義這些流程,程式碼如下:

 1 package com.guigui.common.patterns.template;
 2 
 3 /**
 4  * 炒菜抽象類(定義炒菜的具體步驟)
 5  */
 6 public abstract class AbstractCook {
 7 
 8     protected String dishName = "";
 9 
10     public void fryProcess() {
11         // 設定菜名
12         this.setDishName();
13         // 步驟一: 買菜
14         this.buyDishes();
15         // 步驟二: 洗菜
16         this.washDishes();
17         // 步驟三: 準備菜
18         this.prepareDisher();
19         // 步驟四: 做菜
20         this.fryDishes();
21         // 步驟五: 出鍋
22         this.finishDishes();
23     }
24 
25     // 設定菜名
26     protected abstract void setDishName();
27 
28     // 買菜通用步驟
29     protected void buyDishes() {
30         System.out.println("去菜市場買:" + dishName);
31     }
32 
33     // 洗菜通用步驟
34     protected void washDishes() {
35         System.out.println("將" + dishName + "用水洗乾淨");
36     }
37 
38     // 準備菜(各類菜準備方式不一樣,此處使用抽象方法)
39     protected abstract void prepareDisher();
40 
41     // 做菜(各類菜做法不一樣,此處抽象)
42     protected abstract void fryDishes();
43 
44     // 出鍋
45     protected void finishDishes() {
46         System.out.println("將" + dishName + "盛出並裝盤");
47     }
48 
49 }

       在這裡我們規定了做菜的通用流程,像買菜、洗菜以及出鍋這些步驟,所有菜的做法基本都是一樣的,所以可以將其方法的實現定義在抽象類中,如有特別需要也可對相應步驟進行重寫;而準備菜和做菜這兩個步驟,不同型別的才的做法差異較大,所以需要定義為抽象方法,讓具體的菜做法實現類去實現這兩個步驟。接下來就是定義具體的菜的做法類,我這邊給出了三個具有代表性的菜類的做法,分別是:炒青菜、蒸雞蛋以及燒排骨,下面具體來看一下他們的實現:

1、  炒青菜

 1 package com.guigui.common.patterns.template;
 2 
 3 /**
 4  * 炒青菜類
 5  */
 6 public class CookGreens extends AbstractCook {
 7     @Override
 8     protected void setDishName() {
 9         this.dishName = "青菜";
10     }
11 
12     @Override
13     protected void prepareDisher() {
14         System.out.println("開始摘" + dishName + "...");
15         System.out.println("開始切" + dishName + "...");
16         System.out.println("開始對" + dishName + "進行過水...");
17         System.out.println("開始準備蒜瓣, 並將其切碎...");
18     }
19 
20     @Override
21     protected void fryDishes() {
22         System.out.println("倒油並燒開...");
23         System.out.println("倒入蒜瓣爆香...");
24         System.out.println("倒入" + dishName + "進行翻炒3分鐘...");
25     }
26 }

     實現了準備菜和做菜這兩個抽象方法,內容對應青菜的做法。

2、  蒸雞蛋

 1 package com.guigui.common.patterns.template;
 2 
 3 /**
 4  * 蒸雞蛋類
 5  */
 6 public class CookEggs extends AbstractCook {
 7     @Override
 8     protected void setDishName() {
 9         this.dishName = "雞蛋";
10     }
11 
12     @Override
13     protected void washDishes() {
14         // 雞蛋不需要清洗,這裡用一個空方法重寫覆蓋
15     }
16 
17     @Override
18     protected void prepareDisher() {
19         System.out.println("打破" + dishName + "殼,並放入碗中...");
20         System.out.println("搗碎" + dishName + "蛋黃和蛋清, 攪拌至均勻...");
21         System.out.println("倒入一定量的清水以及油和鹽...");
22     }
23 
24     @Override
25     protected void fryDishes() {
26         System.out.println("在蒸鍋中放入一定量水...");
27         System.out.println("放入隔層...");
28         System.out.println("放入準備好的" + dishName + ", 蓋上鍋蓋蒸10到15分鐘...");
29     }
30 }

       實現蒸雞蛋的準備方法與做法,另外雞蛋不需要洗,所以把洗菜這個方法實現成了一個空的方法,表示這個菜是不需要清洗的。

3、  燒排骨

 1 package com.guigui.common.patterns.template;
 2 
 3 /**
 4  * 燒排骨類
 5  */
 6 public class CookRibs extends AbstractCook{
 7     @Override
 8     protected void setDishName() {
 9         this.dishName = "排骨";
10     }
11 
12     @Override
13     protected void prepareDisher() {
14         System.out.println("剁碎洗乾淨後的" + dishName + "...");
15         System.out.println("準備一定量的蔥薑蒜,並切碎...");
16         System.out.println("將準備好的" + dishName + "在鍋中進行焯水處理後盛出...");
17     }
18 
19     @Override
20     protected void fryDishes() {
21         System.out.println("在鍋中倒入油並燒開...");
22         System.out.println("倒入準備好的蔥薑蒜爆香...");
23         System.out.println("放入焯水後的排骨進行翻炒5到10分鐘...");
24         System.out.println("倒入一定量料酒,及其他調料,繼續翻炒幾分鐘...");
25     }
26 }

      實現了排骨的具體做法。

      以上便是模板方法模式的具體實現;下面繼續介紹另外一個常用的設計模式:策略模式,策略模式經常和模板方法模式結合起來使用,模板方法規定主流程,具體的實現分別對應不同的策略。

三、策略模式

       策略模式屬於物件的行為模式。其用意是針對一組演算法,將每一個演算法封裝到具有共同介面的獨立的類中,從而使得它們可以相互替換。策略模式使得演算法可以在不影響到客戶端的情況下發生變化。

      策略模式是對演算法的包裝,是把使用演算法的責任和演算法本身分割開來,委派給不同的物件管理。策略模式通常把一個系列的演算法包裝到一系列的策略類裡面,作為一個抽象策略類的子類。用一句話來說,就是:“準備一組演算法,並將每一個演算法封裝起來,使得它們可以互換”。下圖為策略模式具體結構:

 

     這個模式涉及到三個角色:

  ●  環境(Context)角色:持有一個Strategy的引用。

  ●  抽象策略(Strategy)角色:這是一個抽象角色,通常由一個介面或抽象類實現。此角色給出所有的具體策略類所需的介面。

  ●  具體策略(ConcreteStrategy)角色:包裝了相關的演算法或行為。

      還是以上述做菜的例子為例說明,做菜的抽象流程類便是抽象策略角色,每一種菜的實現都可以當做是一個具體策略角色,然後我們還需要加上一個環境角色,來完善這個策略模式,下面程式碼示例即為環境角色類:

 1 package com.guigui.common.patterns.strategy;
 2 
 3 import com.guigui.common.patterns.template.AbstractCook;
 4 
 5 public class Context {
 6     // 持有具體策略物件
 7     private AbstractCook cookDish;
 8     // 構造方法,傳入具體策略物件
 9     public Context(AbstractCook cookDish) {
10         this.cookDish = cookDish;
11     }
12     // 執行策略方法
13     public void strategyProcess(){
14         cookDish.fryProcess();
15     }
16 }

      這樣便完成了策略模式的基本結構。下面寫一個示例來演示一下模板方法模式和策略模式結合起來使用過程,以下為測試類程式碼:

 1 package com.guigui.common.patterns;
 2 
 3 import com.guigui.common.patterns.template.AbstractCook;
 4 import com.guigui.common.patterns.template.CookEggs;
 5 import com.guigui.common.patterns.template.CookGreens;
 6 import com.guigui.common.patterns.template.CookRibs;
 7 import com.guigui.common.patterns.strategy.Context;
 8 
 9 import java.util.HashMap;
10 import java.util.Map;
11 
12 public class StrategyMain {
13     private static Map<String, AbstractCook> strategyMap = new HashMap<>();
14     static {
15         strategyMap.put("greens", new CookGreens());
16         strategyMap.put("eggs", new CookEggs());
17         strategyMap.put("ribs", new CookRibs());
18     }
19     public static void main(String[] args) {
20         // 炒青菜
21         Context green_context = new Context(strategyMap.get("greens"));
22         green_context.strategyProcess();
23         System.out.println("==========================================");
24 
25         // 蒸雞蛋
26         Context egg_context = new Context(strategyMap.get("eggs"));
27         egg_context.strategyProcess();
28         System.out.println("==========================================");
29 
30         // 燒排骨
31         Context rib_context = new Context(strategyMap.get("ribs"));
32         rib_context.strategyProcess();
33         System.out.println("==========================================");
34     }
35 }

      這個示例通過使用策略模式來做了三個菜,並且這三個菜的做法是通過一個模板方法來限定基本流程,而不同的做菜類則是不同的做菜策略,只需要將具體的策略例項傳入環境角色,便可以實現對應的做菜流程。

     測試結果:

     好了,今天要介紹的內容大概就是這麼多,有什麼不對的地方歡迎大家指正,非常感謝~~~