設計模式--模板方法模式

模板方法模式

定義

定義一個操做中的算法的框架,而將一些步驟延遲到子類中。使得子類能夠不改變一個算法的結構便可重定義該算法的某些特定步驟。算法

通用類圖

模板方法通用類圖

意圖

定義一個操做中的算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類能夠不改變一個算法的結構便可重定義該算法的某些特定步驟。安全

優勢

  1. 封裝不變部分,擴展可變部分。
  2. 提取公共代碼,便於維護。
  3. 行爲由父類控制,子類實現。

缺點

按照咱們的設計習慣,抽象類負責聲明最抽象、最通常的事物屬性和方法,實現類完成具體的事物屬性和方法。可是模板方法模式卻顛倒了,抽象類定義了部分抽象方法,由子類實現,子類執行的結果影響了父類的結果,也就是子類對父類產生了影響,這在複雜的項目中,會帶來代碼閱讀的難度,並且也會讓新手產生不適感。併發

應用場景

  1. 多個子類有公有的方法,而且邏輯基本相同時。
  2. 重要複雜的算法,能夠把核心算法設計爲模板方法,周邊的相關細節功能則由各個子類實現。
  3. 重構時,模板方法模式是一個常常使用的模式,把相同的代碼 抽取到父類中。

擴展

鉤子函數,經過鉤子函數可讓子類決定父類的行爲。框架

說明

模板方法其實主要是定義一個通用的算法框架(這裏的算法說得是爲解決事情而須要執行的一系列步驟),將通用的抽象的部分定義在父類中,變化的具體的部分只是在父類中聲明抽象的方法,由子類去實現具體的內容。ide

話很少說上代碼,這裏只爲了演示,併發線程安全的問題暫不作考慮。函數

在由事件驅動的系統中,確定會用到消息隊列,這裏模擬一個利用消息隊列處理平時系統產生的各類消息(用戶註冊,下訂單,上傳產品,扣減庫存等)的場景。this

假設有一個全部消息的基本的父類線程

class BaseMsg {
    Date date;

    public Date getDate() {
        return date;
    }

    public void setDate(Date date) {
        this.date = date;
    }
}

消息A設計

class MsgA extends BaseMsg {

    String content;

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}

消息B日誌

class MsgB extends BaseMsg{

    String content;

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}

用 ConcurrentLinkedQueue 模擬消息隊列,現實中可能用到的是 ActiveMQ, RabbitMQ 等

public class CacheBuffer {

    public static final Queue CACHE_BUFFER = new ConcurrentLinkedQueue();

}

下面就是咱們的模版類了

public abstract class AbstractMsgHandler<E extends BaseMsg> {

    private boolean isDo = true;

    protected void put(E e) {
        CACHE_BUFFER.offer(e);
    }

    protected void process() {
        E e = (E) CACHE_BUFFER.poll();

        if (checkValid(e)) {

            setProcessTime(e);

            if (isDo()) {
                doAnotherThing();
            }

            doLogic(e);
        }
    }

    private boolean checkValid(E e) {
        System.out.println("檢查字段是否爲空,對象是否合法等等");
        return true;
    }

    private void setProcessTime(E e) {
        System.out.println("設置處理時間等於當前時間,記錄到日誌");
    }

    private void doAnotherThing() {
        System.out.println("作其餘的特別的事情");
    }

    protected abstract void doLogic(E e);

    protected boolean isDo () {
        return isDo;
    }

    public void setDo(boolean aDo) {
        isDo = aDo;
    }
}

在模版類中添加了一些通用的方法

put 方法的是添加消息到隊列
process 方法的是從消息隊列中取出消息,而且檢查了有效性(checkValid),設置了處理時間(setProcessTime),還有其餘事情(doAnotherThing),接下來是處理業務邏輯(doLogic)

接下來是 消息A的處理類

public class ConcreteMsgAHandler extends AbstractMsgHandler<MsgA> {

    @Override
    protected void doLogic(MsgA msgA) {
        System.out.println("MsgA 處理業務邏輯");
    }

}

消息B的處理類

public class ConcreteMsgBHandler extends AbstractMsgHandler<MsgB> {

    @Override
    protected void doLogic(MsgB msgB) {
        System.out.println("MsgB 處理業務邏輯");
    }
}

各自實現了 doLogic 方法,實現本身的業務邏輯。

場景類

public static void main(String[] args) {
    MsgA msgA = new MsgA();
    msgA.setContent("a");

    MsgB msgB = new MsgB();
    msgB.setContent("b");

    ConcreteMsgAHandler msgAHandler = new ConcreteMsgAHandler();
    ConcreteMsgBHandler msgBHandler = new ConcreteMsgBHandler();
    
    msgAHandler.put(msgA);
    msgBHandler.put(msgB);
    
    msgAHandler.process();
    msgBHandler.process();
}

運行結果爲

檢查字段是否爲空,對象是否合法等等
設置處理時間等於當前時間,記錄到日誌
作其餘的特別的事情
MsgA 處理業務邏輯

檢查字段是否爲空,對象是否合法等等
設置處理時間等於當前時間,記錄到日誌
作其餘的特別的事情
MsgB 處理業務邏輯

實現了模版方法的子類(ConcreteMsgAHandler, ConcreteMsgBHandler)在執行了 process 方法以後都會檢查字段是否爲空,設置處理時間,作其餘事情,而後作本身的業務。
process 方法爲模板方法,裏面的代碼塊即咱們這裏的算法框架,定義了通用的算法,或者說封裝了不變的部分。doLogic 方法由子類實現具體的邏輯。

這樣若是還有一個消息C(MsgC),咱們只要建立一個 ConcreteMsgCHandler 而且實現 doLogic 方法便可。

擴展和維護起來很是方便。修改通用算法只需修改模板方法,擴展只需新建子類。

在模板方法中還有一個 setDo 方法,這個方法便是模板的方法的鉤子函數,若是某個子類不須要通用算法的某個部分,能夠經過 setDo 來改變父類通用算法的行爲。

public static void main(String[] args) {
    MsgA msgA = new MsgA();
    msgA.setContent("a");

    MsgB msgB = new MsgB();
    msgB.setContent("b");

    ConcreteMsgAHandler msgAHandler = new ConcreteMsgAHandler();
    ConcreteMsgBHandler msgBHandler = new ConcreteMsgBHandler();

    msgAHandler.put(msgA);
    msgBHandler.put(msgB);


    msgAHandler.get();
    msgBHandler.setDo(false);
    msgBHandler.get();
}

輸出的結果爲

檢查字段是否爲空,對象是否合法等等
設置處理時間等於當前時間,記錄到日誌
作其餘的特別的事情
MsgA 處理業務邏輯

檢查字段是否爲空,對象是否合法等等
設置處理時間等於當前時間,記錄到日誌
MsgB 處理業務邏輯

對比上面的輸出就能夠發現消息B再也不 作其餘的特別的事情

相關文章
相關標籤/搜索