CRUD很無聊?一起學設計模式吧!–模板模式
- 2019 年 11 月 20 日
- 筆記
定義與特點
模板方法(Template Method)模式的定義如下: 定義一個操作中的算法骨架,而將算法的一些步驟延遲到子類中,使得子類可以不改變該算法結構的情況下重定義該算法的某些特定步驟。它是一種類行為型模式。
模板模式的主要優點如下:
- 它封裝了不變部分,擴展可變部分。它把認為是不變部分的算法封裝到父類中實現,而把可變部分算法由子類繼承實現,便於子類繼續擴展。
- 它在父類中提取了公共的部分代碼,便於代碼復用。
- 部分方法是由子類實現的,因此子類可以通過擴展方式增加相應的功能,符合開閉原則。
主要缺點如下:
- 對每個不同的實現都需要定義一個子類,這會導致類的個數增加,系統更加龐大,設計也更加抽象。
- 父類中的抽象方法由子類實現,子類執行的結果會影響父類的結果,這導致一種反向的控制結構,它提高了代碼閱讀的難度。
UML
角色定義
模板模式涉及三個角色:
- 抽象類(AbstractClass)角色:定義一個操作的算法輪廓和框架。它由一個模板方法和若干個基本方法組成。模板方法(templateMethod):定義了算法的骨架,按某種順序調用其包含的基本方法,使用public修飾;基本方法:是整個算法中的一個步驟,使用protected修飾,包含以下幾個類型:
- 抽象方法:在抽象類中申明,由具體子類實現
- 具體方法:在抽象類中實現,但是子類可以繼承或重寫它。
- 鉤子方法:在抽象類中已經實現,包括用於判斷的邏輯方法和需要子類重寫的空方法兩種。
場景實戰
我們的報銷系統分為日常費用報銷和差旅費用報銷,報銷的流程是先根據報銷單上帶的費用計算出報銷金額,然後計算出報銷單中的補貼金額(若是差旅類型報銷才需要計算補貼,日常報銷不需要計算補貼),最後調用第三方接口創建流程。這個場景就適合用模板設計模式實現。
抽象類定義
定義報銷流程的算法框架,算法框架使用final
修飾,對於必須要子類實現的方法用abstract
關鍵字修飾。
public abstract class AbstractReimburse { /** * 用作算法的模板 * 定義成final,以免子模板改變算法的順序 */ final void calAndCreateFlow(){ BigDecimal totalMoney; BigDecimal changeMoney = calChangeMoney(); BigDecimal subsidyMoney = BigDecimal.ZERO; if(hasTravel()){ subsidyMoney = calSubsidyMoney(); } totalMoney = changeMoney.add(subsidyMoney); createWorkeFlow(totalMoney); } /** * 具體方法,子類判斷是否需要實現 * @param totalMoney 報銷總金額 */ protected void createWorkeFlow(BigDecimal totalMoney) { System.out.println("開始創建流程...,總報銷金額:"+totalMoney); //todo } /** * 鉤子方法,由子類決定是否實現,鉤子可以作為條件控制,影響抽象類中的算法流程 * 判斷是否需要計算補貼 */ boolean hasTravel() { return false; } /** * 抽象方法,需要子類去實現 * 返回需要報銷的費用金額 * @return 報銷的費用總金額 */ abstract BigDecimal calChangeMoney(); /** * 返回需要報銷的補貼金額 */ abstract BigDecimal calSubsidyMoney(); }
具體實現類
- 差旅類報銷實現邏輯
/** * 差旅類報銷實現邏輯 */ public class TravelReimburse extends AbstractReimburse{ @Override BigDecimal calChangeMoney() { System.out.println("差旅類報銷計算費用金額"); return new BigDecimal(1000); } @Override BigDecimal calSubsidyMoney() { System.out.println("差旅類報銷需要計算補貼"); return new BigDecimal(500); } @Override boolean hasTravel(){ return true; } }
- 日常類報銷實現邏輯
/** * 日常類報銷實現邏輯 */ public class DailyReimburse extends AbstractReimburse{ @Override BigDecimal calChangeMoney() { System.out.println("日常類報銷計算費用金額"); return new BigDecimal(100); } @Override BigDecimal calSubsidyMoney() { return BigDecimal.ZERO; } }
客戶端模擬業務流程
public class ReimburseClient { public static void main(String[] args) { //差旅類報銷處理邏輯 TravelReimburse travelReimburse = new TravelReimburse(); travelReimburse.calAndCreateFlow(); System.out.println("==========================="); //日常類報銷處理邏輯 DailyReimburse dailyReimburse = new DailyReimburse(); dailyReimburse.calAndCreateFlow(); } }
運行結果
應用場景
模板模式應該是眾多設計模式中相對簡單的一種,但是它使用的頻率可一點也不低,在各種開源框架代碼中都可以看到它的身影,模板設計模式的應用場景主要有以下幾類:
- 在多個子類中擁有相同的方法,而且邏輯相同,可以將這些方法抽出來放到一個模板抽象類中
- 程序主框架相同,僅實現細節不同時,也可以使用模板方法
tips 記得幾年前電話面試的時候,面試官問我有沒有用過模板設計模式,我回答說「啊,模板?你說的是freemarker嗎?巴拉巴拉一大堆」,然後只聽電話嘟嘟嘟響,留我一人在風中凌亂。