菜鳥成長系列-模板方法模式

glmapper發表於2018-04-30

模板方法模式在sring中有大量的應用,一般我們會使用模板方法來將當前的實現委託給子類來實現,增強程式碼的可擴充套件性和複用性。因為涉及到父子類關係,所以模板方法模式是基於“繼承”來實現的;模板方法模式屬於行為型模式。

簡單地說就是,通過父類來定義一系列的演算法骨架,並且約定這些方法及其呼叫順序,而具體的某些特定方法由子類實現。

先來看一個小demo;我們以寫部落格來舉例子,一般我們寫部落格的步驟如下:

  • 開啟目標網站
  • 開啟編輯器
  • 寫文章
  • 釋出文章

例項程式碼

首先是定義一個父類,並且提供一個模板方法。

package com.glmapper.designmode.mudolmethod;
/**
 * @description: 抽象模板父類
 * @email: <a href="glmapper_2018@163.com"></a>
 * @author: 磊叔
 * @date: 18/4/30
 */
public abstract class AbstractTemplateMethod {
    /**
     * 流程方法1:抽象方法,供子類實現
     */
    public abstract void openTargetWebSite();

    /**
     * 流程方法2:子類可選擇重寫
     */
    public void openMarkDown(){
        System.out.println("開啟編輯器");
    }

    /**
     * 流程方法3:抽象方法,供子類實現
     */
    public abstract void writeBlog();

    /**
     * 流程方法4:子類可選擇重寫
     */
    protected void publisher(){
        System.out.println("釋出文章");
    }

    /**
     * 模板方法,此處申明為final,是不希望子類覆蓋這個方法,防止更改流程的執行順序
     */
    public final void templateWriteBlog(){
        openTargetWebSite();
        openMarkDown();
        writeBlog();
        publisher();
    }
}

複製程式碼

上面程式碼中我們提供了一個templateWriteBlog方法,這裡方法中包括了寫部落格的一些流程。在這些流程方法中有些方法父類提供了預設實現,而一些具有差異性的方法則讓子類來實現。

package com.glmapper.designmode.mudolmethod;
/**
 * @description: 子類
 * @email: <a href="glmapper_2018@163.com"></a>
 * @author: 磊叔
 * @date: 18/4/30
 */
public class JueJinTemplateMethodPolicy extends AbstractTemplateMethod {
    @Override
    public void openTargetWebSite() {
        System.out.println("開啟掘金網站");
    }

    @Override
    public void writeBlog() {
        System.out.println("寫一篇Spring相關的文章");
    }
}
複製程式碼

子類1:JueJinTemplateMethodPolicy,這個子類中實現了父類中的部分方法,包括:openTargetWebSite和writeBlog。(一般情況下不會去重寫父類預設已經實現的方法,僅實現父類中預留的抽象方法來實現)。

package com.glmapper.designmode.mudolmethod;

/**
 * @description: 子類
 * @email: <a href="glmapper_2018@163.com"></a>
 * @author: 磊叔
 * @date: 18/4/30
 */
public class CSDNTemplateMethodPolicy extends AbstractTemplateMethod{

    @Override
    public void openTargetWebSite() {
        System.out.println("開啟CSDN網站");
    }

    @Override
    public void writeBlog() {
        System.out.println("寫一篇設計模式文章");
    }
}

複製程式碼

子類2:CSDNTemplateMethodPolicy,這個子類的作用其實和子類1是一樣的,只不過是提供了另外的一種實現策略;(很多情況下,模板方法模式都是和策略模式來聯合使用的,通過一套模板機制,對於模板中的部分流程通過不同的策略來實現不同的功能)

package com.glmapper.designmode.mudolmethod;
/**
 * @description: 子類
 * @email: <a href="glmapper_2018@163.com"></a>
 * @author: 磊叔
 * @date: 18/4/30
 */
public class MainTest {
    public static void main(String[] args) {
        AbstractTemplateMethod csdnTemplate = new CSDNTemplateMethodPolicy();
        csdnTemplate.templateWriteBlog();

        AbstractTemplateMethod juejinTemplate = new JueJinTemplateMethodPolicy();
        juejinTemplate.templateWriteBlog();
    }
}

開啟CSDN網站
開啟編輯器
寫一篇設計模式文章
釋出文章

開啟掘金網站
開啟編輯器
寫一篇Spring相關的文章
釋出文章
複製程式碼

上面是客戶端程式碼及輸出結果。通過輸出我們可以明顯的看出,模板中的一些方法將延遲到子類中去實現,模板方法使得子類可以不改變一個演算法的結構即可重定義該演算法的某些特定步驟。因此對於模板方法這個模式來說,父類是始終控制著整個流程主動權的,而子類只是輔助父類實現某些可定製的步驟。

模式解析

先看下模板方法模式的類圖:

菜鳥成長系列-模板方法模式

從類圖中可以看出,模板方法模式中的角色也是很簡單的,主要包括兩個角色:

  • 抽象模板(AbstractTemplate):

    • 定義一個或者多個抽象操作,以便於讓子類實現。這些抽象操作就是流程中的基本操作(對應的是模板方法中的某個具體的操作方法);這些基本操作是一個頂級邏輯的組成步驟
    • 定義並且實現了一個模板方法。這個模板方法一般是一個具體方法,它給出了一個頂級邏輯的骨架,而邏輯的組成步驟在相應的抽象操作中,推遲到子類中取實現,當然,在這個頂級邏輯中,部分方法也可以由父類來提供預設實現的。
  • 具體類(SubTemplateImpl):

    • 實現父類所定義的一個或者多個抽象方法
    • 每一個抽象模板角色都可以有任意多個具體模板角色與之對應,而每一個具體模板角色都可以給出這些抽象方法的不同實現。

模板方法中的這個方法的概念拆開來說包括兩種,一種是模板方法,還有一種是模板方法裡面的基本方法。模板方法定義遊戲規則,基本方法實現規則中的每個部分。

模板方法帶來的優勢是顯而易見的,它可以幫助我們有效的幫助我們搞定下面的這些場景問題:

  • 封裝不變部分,擴充套件可變部分。
  • 提取公共程式碼,便於維護。
  • 行為由父類控制,子類實現。

但是缺點也很明顯,因為對於每一個不同的實現都需要一個子類來實現,導致類的個數增加,使得系統更加龐大。

典型的模板方法模式的應用

最先想到的就是servlet,servlet的生命週期(以前經常遇到的面試點,現在已經沒人問了吧)

  • 初始化 init
  • 處理 service
  • 銷燬 destroy

其實這裡我覺得也是模板方法的一種體現,雖然在servlet中沒有定義頂層的模板方法來控制這個流程(我的想法是這個流程是由容器來控制的,也可能是一種預設的約定)。

在其子類GenericServlet中對init和destroy有了預設的實現,而service方法則是交由子類來實現的,也就是說任何servlet類均必須實現service方法。

這裡的service方法就是一個模板方法。service方法中呼叫了7個do方法中的一個或者幾個,完成對客戶端的響應,這些do方法需要由HttpServlet的具體子類提供。

HttpServlet中的實現:

protected void service(HttpServletRequest req, HttpServletResponse
resp) throws ServletException, IOException {
        String method = req.getMethod();
        long lastModified;
        if (method.equals("GET")) {
            lastModified = this.getLastModified(req);
            if (lastModified == -1L) {
                this.doGet(req, resp);
            } else {
                long ifModifiedSince =
                req.getDateHeader("If-Modified-Since");
                if (ifModifiedSince < lastModified) {
                    this.maybeSetLastModified(resp, lastModified);
                    this.doGet(req, resp);
                } else {
                    resp.setStatus(304);
                }
            }
        } else if (method.equals("HEAD")) {
            lastModified = this.getLastModified(req);
            this.maybeSetLastModified(resp, lastModified);
            this.doHead(req, resp);
        } else if (method.equals("POST")) {
            this.doPost(req, resp);
        } else if (method.equals("PUT")) {
            this.doPut(req, resp);
        } else if (method.equals("DELETE")) {
            this.doDelete(req, resp);
        } else if (method.equals("OPTIONS")) {
            this.doOptions(req, resp);
        } else if (method.equals("TRACE")) {
            this.doTrace(req, resp);
        } else {
            String errMsg =
            lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[]{method};
            errMsg = MessageFormat.format(errMsg, errArgs);
            resp.sendError(501, errMsg);
        }

    }
複製程式碼

FrameworkServlet中的實現(FrameworkServlet是SpringMVC核心控制器DispatchServlet的父類):

/**
 * Override the parent class implementation in order to intercept
 PATCH requests.
 */
@Override
protected void service(HttpServletRequest request,
HttpServletResponse response)
		throws ServletException, IOException {

	HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
	if (HttpMethod.PATCH == httpMethod || httpMethod == null) {
		processRequest(request, response);
	}
	else {
		super.service(request, response);
	}
}
複製程式碼

關於模板方法模式的學習就到這裡了。五一快樂!!!

相關文章