模板方法(Template Method)模式

  模板方法模式是類的行爲模式。準備一個抽象類,將部分邏輯以具體方法以及具體構造函數的形式實現,而後聲明一些抽象方法來迫使子類實現剩餘的邏輯。不一樣的子類能夠以不一樣的方式實現這些抽象方法,從而對剩餘的邏輯有不一樣的實現。這就是模板方法模式的用意。web

模板方法模式的結構

  模板方法模式是全部模式中最爲常見的幾個模式之一,是基於繼承的代碼複用的基本技術。算法

  模板方法模式須要開發抽象類和具體子類的設計師之間的協做。一個設計師負責給出一個算法的輪廓和骨架,另外一些設計師則負責給出這個算法的各個邏 輯步驟。表明這些具體邏輯步驟的方法稱作基本方法(primitive method);而將這些基本方法彙總起來的方法叫作模板方法(template method),這個設計模式的名字就是今後而來。設計模式

  模板方法所表明的行爲稱爲頂級行爲,其邏輯稱爲頂級邏輯。模板方法模式的靜態結構圖以下所示:less

  

  這裏涉及到兩個角色:ide

  抽象模板(Abstract Template)角色有以下責任:函數

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

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

  具體模板(Concrete Template)角色又以下責任:設計

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

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

  源代碼

  抽象模板角色類,abstractMethod()、hookMethod()等基本方法是頂級邏輯的組成步驟,這個頂級邏輯由templateMethod()方法表明。

public abstract class AbstractTemplate {
    /**
     * 模板方法
     */
    public void templateMethod(){
        //調用基本方法
        abstractMethod();
        hookMethod();
        concreteMethod();
    }
    /**
     * 基本方法的聲明(由子類實現)
     */
    protected abstract void abstractMethod();
    /**
     * 基本方法(空方法)
     */
    protected void hookMethod(){}
    /**
     * 基本方法(已經實現)
     */
    private final void concreteMethod(){
        //業務相關的代碼
    }
}

  具體模板角色類,實現了父類所聲明的基本方法,abstractMethod()方法所表明的就是強制子類實現的剩餘邏輯,而hookMethod()方法是可選擇實現的邏輯,不是必須實現的。

public class ConcreteTemplate extends AbstractTemplate{
    //基本方法的實現
    @Override
    public void abstractMethod() {
        //業務相關的代碼
    }
    //重寫父類的方法
    @Override
    public void hookMethod() {
        //業務相關的代碼
    }
}

  模板模式的關鍵是:子類能夠置換掉父類的可變部分,可是子類卻不能夠改變模板方法所表明的頂級邏輯。

  每當定義一個新的子類時,不要按照控制流程的思路去想,而應當按照「責任」的思路去想。換言之,應當考慮哪些操做是必須置換掉的,哪些操做是能夠置換掉的,以及哪些操做是不能夠置換掉的。使用模板模式可使這些責任變得清晰。

模板方法模式中的方法

  模板方法中的方法能夠分爲兩大類:模板方法和基本方法。

  模板方法

  一個模板方法是定義在抽象類中的,把基本操做方法組合在一塊兒造成一個總算法或一個總行爲的方法。

  一個抽象類能夠有任意多個模板方法,而不限於一個。每個模板方法均可以調用任意多個具體方法。

  基本方法

  基本方法又能夠分爲三種:抽象方法(Abstract Method)、具體方法(Concrete Method)和鉤子方法(Hook Method)。

  ●  抽象方法:一個抽象方法由抽象類聲明,由具體子類實現。在Java語言裏抽象方法以abstract關鍵字標示。

  ●  具體方法:一個具體方法由抽象類聲明並實現,而子類並不實現或置換。

  ●  鉤子方法:一個鉤子方法由抽象類聲明並實現,而子類會加以擴展。一般抽象類給出的實現是一個空實現,做爲方法的默認實現。

  在上面的例子中,AbstractTemplate是一個抽象類,它帶有三個方法。其中abstractMethod()是一個抽象方法,它由 抽象類聲明爲抽象方法,並由子類實現;hookMethod()是一個鉤子方法,它由抽象類聲明並提供默認實現,而且由子類置換掉。 concreteMethod()是一個具體方法,它由抽象類聲明並實現。

  默認鉤子方法

  一個鉤子方法經常由抽象類給出一個空實現做爲此方法的默認實現。這種空的鉤子方法叫作「Do Nothing Hook」。顯然,這種默認鉤子方法在缺省適配模式裏面已經見過了,一個缺省適配模式講的是一個類爲一個接口提供一個默認的空實現,從而使得缺省適配類的 子類沒必要像實現接口那樣必須給出全部方法的實現,由於一般一個具體類並不須要全部的方法。

  命名規則

  命名規則是設計師之間賴以溝通的管道之一,使用恰當的命名規則能夠幫助不一樣設計師之間的溝通。

  鉤子方法的名字應當以do開始,這是熟悉設計模式的Java開發人員的標準作法。在上面的例子中,鉤子方法hookMethod()應當以do開頭;在HttpServlet類中,也聽從這一命名規則,如doGet()、doPost()等方法。

使用場景

  考慮一個計算存 款利息的例子。假設系統須要支持兩種存款帳號,即貨幣市場(Money Market)帳號和按期存款(Certificate of Deposite)帳號。這兩種帳號的存款利息是不一樣的,所以,在計算一個存戶的存款利息額時,必須區分兩種不一樣的帳號類型。

  這個系統的總行爲應當是計算出利息,這也就決定了做爲一個模板方法模式的頂級邏輯應當是利息計算。因爲利息計算涉及到兩個步驟:一個基本方法給出帳號種類,另外一個基本方法給出利息百分比。這兩個基本方法構成具體邏輯,由於帳號的類型不一樣,因此具體邏輯會有所不一樣。

  顯然,系統須要一個抽象角色給出頂級行爲的實現,而將兩個做爲細節步驟的基本方法留給具體子類實現。因爲須要考慮的帳號有兩種:一是貨幣市場帳號,二是按期存款帳號。系統的類結構以下圖所示。

  源代碼

  抽象模板角色類

public abstract class Account {
    /**
     * 模板方法,計算利息數額
     * @return    返回利息數額
     */
    public final double calculateInterest(){
        double interestRate = doCalculateInterestRate();
        String accountType = doCalculateAccountType();
        double amount = calculateAmount(accountType);
        return amount * interestRate;
    }
    /**
     * 基本方法留給子類實現
     */
    protected abstract String doCalculateAccountType();
    /**
     * 基本方法留給子類實現
     */
    protected abstract double doCalculateInterestRate();
    /**
     * 基本方法,已經實現
     */
    private double calculateAmount(String accountType){
        /**
         * 省略相關的業務邏輯
         */
        return 7243.00;
    }
}

  具體模板角色類

public class MoneyMarketAccount extends Account {

    @Override
    protected String doCalculateAccountType() {
        
        return "Money Market";
    }

    @Override
    protected double doCalculateInterestRate() {
        
        return 0.045;
    }

}
public class CDAccount extends Account {

    @Override
    protected String doCalculateAccountType() {
        return "Certificate of Deposite";
    }

    @Override
    protected double doCalculateInterestRate() {
        return 0.06;
    }

}

客戶端類

public class Client {

    public static void main(String[] args) {
        Account account = new MoneyMarketAccount();
        System.out.println("貨幣市場帳號的利息數額爲:" + account.calculateInterest());
        account = new CDAccount();
        System.out.println("按期帳號的利息數額爲:" + account.calculateInterest());
    }

}

模板方法模式在Servlet中的應用

  使用過 Servlet的人都清楚,除了要在web.xml作相應的配置外,還需繼承一個叫HttpServlet的抽象類。HttpService類提供了一個 service()方法,這個方法調用七個do方法中的一個或幾個,完成對客戶端調用的響應。這些do方法須要由HttpServlet的具體子類提供, 所以這是典型的模板方法模式。下面是service()方法的源代碼:

protected void service(HttpServletRequest req, HttpServletResponse resp)
        throws ServletException, IOException {

        String method = req.getMethod();

        if (method.equals(METHOD_GET)) {
            long lastModified = getLastModified(req);
            if (lastModified == -1) {
                // servlet doesn't support if-modified-since, no reason
                // to go through further expensive logic
                doGet(req, resp);
            } else {
                long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
                if (ifModifiedSince < (lastModified / 1000 * 1000)) {
                    // If the servlet mod time is later, call doGet()
                    // Round down to the nearest second for a proper compare
                    // A ifModifiedSince of -1 will always be less
                    maybeSetLastModified(resp, lastModified);
                    doGet(req, resp);
                } else {
                    resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
                }
            }

        } else if (method.equals(METHOD_HEAD)) {
            long lastModified = getLastModified(req);
            maybeSetLastModified(resp, lastModified);
            doHead(req, resp);

        } else if (method.equals(METHOD_POST)) {
            doPost(req, resp);
            
        } else if (method.equals(METHOD_PUT)) {
            doPut(req, resp);        
            
        } else if (method.equals(METHOD_DELETE)) {
            doDelete(req, resp);
            
        } else if (method.equals(METHOD_OPTIONS)) {
            doOptions(req,resp);
            
        } else if (method.equals(METHOD_TRACE)) {
            doTrace(req,resp);
            
        } else {
            //
            // Note that this means NO servlet supports whatever
            // method was requested, anywhere on this server.
            //

            String errMsg = lStrings.getString("http.method_not_implemented");
            Object[] errArgs = new Object[1];
            errArgs[0] = method;
            errMsg = MessageFormat.format(errMsg, errArgs);
            
            resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
        }
    }

  固然,這個service()方法也能夠被子類置換掉。

  下面給出一個簡單的Servlet例子:

    

  從上面的類圖能夠看出,TestServlet類是HttpServlet類的子類,而且置換掉了父類的兩個方法:doGet()和doPost()。

public class TestServlet extends HttpServlet {

    public void doGet(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        
        System.out.println("using the GET method");

    }

    public void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
            
        System.out.println("using the POST method");
    }

}

  從上面的例子能夠看出這是一個典型的模板方法模式。

  HttpServlet擔任抽象模板角色

    模板方法:由service()方法擔任。

    基本方法:由doPost()、doGet()等方法擔任。

  TestServlet擔任具體模板角色

TestServlet置換掉了父類HttpServlet中七個基本方法中的其中兩個,分別是doGet()和doPost()。

相關文章
相關標籤/搜索