模板方法模式(Template Method Pattern)

2020年9月13日13:50:39

定義(what)

科比會三步上籃,我會三步上籃

科比會投籃,我會投籃

科比會打鐵,我會打鐵

科比 = 我

Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template method lets subclasses redefine certain steps of an algorithm without changing the algorithm』s structure. — 《Design Patterns: Elements of Reusable Object-Oriented Software》

定義一個操作中算法的骨架,而將一個步驟延遲到子類。模板方法使得子類可以不改變一個算法的結構即可重定義該算法的某些特定步驟。 —— 《設計模式:可復用面向對象軟件的基礎》

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

圖示

模板方法模式結構圖(structure diagram):

模板方法模式結構圖

AbstractTemplate是抽象類,定義並實現了一個模板方法templateMethod。這個模板方法一般是一個具體方法,它給出了一個頂級邏輯的骨架,而邏輯的組成步驟在相應的抽象操作(step1、step2)中,推遲到子類實現。

角色

抽象模板(Abstract Template):

  • 定義了一個或者多個抽象操作(step1、step2),推遲到子類實現。
  • 定義了一個模板方法(templateMethod),是一個具體方法,給出了頂級邏輯的架構,頂層邏輯由抽象操作組成。
  • 也會有具體方法,這個方法就是公共方法。

具體模板(Concrete Template):

  • 實現抽象模板的抽象操作

代碼示例

以籃球三步上籃作為代碼示例。首先,從隊友傳球給你,你接球運球一步兩步上籃

代碼示例類圖:

模板方法代碼示例類圖

抽象模板(Layup.java)

// 上籃
public abstract class Layup {

    public final boolean layupTemplate() {
        boolean flag = catchTheBall();
        flag = dribble(flag);
        flag = stride(flag);
        flag = throwOrLayTheBall(flag);
        return score(flag);
    }

    public final boolean catchTheBall() {
        System.out.println("接球");
        return true;
    }

    protected abstract boolean dribble(boolean flag);
    protected abstract boolean stride(boolean flag);
    protected abstract boolean throwOrLayTheBall(boolean flag);

    public final boolean score(boolean flag) {
        if (flag) {
            System.out.println("上籃成功,得分");
            return true;
        } else {
            System.out.println("上籃失敗");
            return false;
        }

    }
}

方法layupTemplate()是模板方法,其中有公共方法(catchTheBall、score),有抽象方法(dribble、stride、throwOrLayTheBall)需要推遲到子類實現。

具體模板(KobeLayup.java、RookieLayup.java):

// 科比上籃
public class KobeLayup extends Layup {

    @Override
    public boolean dribble(boolean flag) {
        if (!flag) return false;
        System.out.println("科比運球,來個crossover過人");
        return true;
    }

    @Override
    public boolean stride(boolean flag) {
        if (!flag) return false;
        System.out.println("科比三分線處上籃起步,一步,二步");
        return true;
    }

    @Override
    public boolean throwOrLayTheBall(boolean flag) {
        if (!flag) return false;
        System.out.println("科比起飛,手輕輕一挑,球空心入框");
        return true;
    }
}

// 菜鳥上籃
public class RookieLayup extends Layup {

     private Random random = new Random();

    @Override
    public boolean dribble(boolean flag) {
        if (!flag) return false;
        if (random.nextInt(100) > 60) {
            System.out.println("菜鳥運球");
            return true;
        } else {
            System.out.println("菜鳥運球,手滑了,球丟了");
            return false;
        }
    }

    @Override
    public boolean stride(boolean flag) {
        if (!flag) return false;
        if (random.nextInt(100) > 60) {
            System.out.println("菜鳥上籃起步,一步,兩步");
            return true;
        } else {
            System.out.println("菜鳥上籃起步,一步,兩步,三步,走步了");
            return false;
        }
    }

    @Override
    public boolean throwOrLayTheBall(boolean flag) {
        if (!flag) return false;
        if (random.nextInt(100) > 60) {
            System.out.println("菜鳥瞄準籃筐,手挑一個,球進了");
            return true;
        } else {
            System.out.println("菜鳥瞄準籃筐,手挑一個,太用力了,哐,打鐵了");
            return false;
        }
    }
}

具體模板實現了抽象模板中抽象方法,不同的具體模板有不同的行為。

測試類:

public class TemplateMethodTest {
    public static void main(String[] args) {
        Layup layup;

        // 科比上籃
        layup = new KobeLayup();
        layup.layupTemplate();

        System.out.println("--------------------------------------------");
        // 菜鳥上籃
        layup = new RookieLayup();
        boolean flag;
        int i = 1;
        do {
            System.out.println("菜鳥第" + i++ + "次上籃");
            flag = layup.layupTemplate();
        } while (!flag);

    }
}

執行結果:
模板方方法模式測試結果

使用場景

  • 模板方法模式解決的問題是使用一種有不同變體的算法。你需要將算法分解為不同的步驟,在不同實現之間有共同點時,在抽象類中實現。另一方面,不同的步驟將在具體類中實現。
  • 在不同的類之間有複製粘貼代碼(私有函數)
  • 當你的大多數類具有相關行為時,你可以使用此模式

實例:Spring JdbcTemplate,將一個公共方法封裝起來,設置數據源(setDataSoure)、獲取會話(getSession)、關閉會話(closeSession)等,當然具體查詢方法也實現了,如果你想重寫也是可以的。

優點

  • 將公共方法放到抽象模板中,減少重複代碼
  • 具體模板類易於增加,刪除,修復

缺點

  • 具體模板增加,系統複雜度增大

總結

模板方法模式是一種行為型模式,有兩種角色:

  • 抽象模板定義了模板方法,方法中包含了一些步驟(頂層邏輯),這些步驟有的是具體方法(公共方法),有的是抽象方法;
  • 具體模板實現了抽象模板中的抽象方法,將變化延遲到子類

參考

Design Patterns: Template Method

2020年9月13日17:22:44