java設(shè)計(jì)模式-模板方法模式(Template Method)

定義

模板方法模式是類的行為模式。準(zhǔn)備一個(gè)抽象類,將部分邏輯以具體方法以及具體構(gòu)造函數(shù)的形式實(shí)現(xiàn),然后聲明一些抽象方法來(lái)迫使子類實(shí)現(xiàn)剩余的邏輯。不同的子類可以以不同的方式實(shí)現(xiàn)這些抽象方法,從而對(duì)剩余的邏輯有不同的實(shí)現(xiàn)。這就是模板方法模式的用意。

模板方法模式的結(jié)構(gòu)

模板方法模式是所有模式中最為常見的幾個(gè)模式之一,是基于繼承的代碼服用的基本技術(shù)。

模板方法模式需要開發(fā)抽象類和具體子類的設(shè)計(jì)師之間的協(xié)作。一個(gè)設(shè)計(jì)師負(fù)責(zé)給出一個(gè)算法的輪廓和骨架,另一些設(shè)計(jì)師則負(fù)責(zé)給出這個(gè)算法的各個(gè)邏輯步驟。代表這些具體邏輯步驟的方法稱作基本方法(primitive method); 而將這些基本方法匯總起來(lái)的方法叫做模板方法(template method),這個(gè)設(shè)計(jì)模式的名字就是由此而來(lái)。

模板方法所代表的行為稱為頂級(jí)行為,其邏輯稱為頂級(jí)邏輯。模板方法模式的靜態(tài)結(jié)構(gòu)圖如下所示:

模板方法模式的靜態(tài)結(jié)構(gòu)圖

這里涉及到兩個(gè)角色:

抽象模板(Abstract Template)角色,具有如下責(zé)任:

  1. 定義了一個(gè)或多個(gè)抽象操作以便讓子類實(shí)現(xiàn)。這些抽象操作叫做基本操作。他們是一個(gè)頂級(jí)邏輯的組成結(jié)構(gòu)。
  2. 定義并實(shí)現(xiàn)了一個(gè)模板方法。這個(gè)模板方法一般是一個(gè)具體方法,它給出了一個(gè)頂級(jí)邏輯的骨架,而邏輯的組成步驟在相應(yīng)的抽象操作中,推遲到子類實(shí)現(xiàn)。頂級(jí)邏輯也有可能調(diào)用一些具體方法。

具體模板(Concrete Template)角色,具有如下責(zé)任:

  1. 實(shí)現(xiàn)父類所定義的一個(gè)或多個(gè)抽象方法,他們是一個(gè)頂級(jí)邏輯的組成步驟。
  2. 每一個(gè)抽象模板角色都可以有任意多個(gè)具體模板角色與之對(duì)應(yīng),而每一個(gè)具體模板角色都可以給出這些抽象方法(也就是頂級(jí)邏輯的組成步驟)的不同實(shí)現(xiàn),從而使得頂級(jí)邏輯的實(shí)現(xiàn)各不相同。

示例代碼

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

public abstract class AbstractTemplate {
    /**
     * 模板方法
     */
    public void templateMethod() {
        //調(diào)用基本方法
        abstractMethod();
        hookMethod();
        concreteMethod();
    }
    /**
     * 基本方法的聲明(由子類實(shí)現(xiàn))
     */
    protected abstract void abstractMethod();
    /**
     * 基本方法(空方法)
     */
    protected void hookMethod(){}
    /**
     * 基本方法(已經(jīng)實(shí)現(xiàn))
     */
    protected final void concreteMethod() {
        // TODO 業(yè)務(wù)相關(guān)的代碼
    }
}

具體模板角色類,實(shí)現(xiàn)了父類所聲明的基本方法,abstractMethod()方法代表的就是強(qiáng)制子類實(shí)現(xiàn)的剩余邏輯,而hookMethod()方法是可以選擇實(shí)現(xiàn)的邏輯,不是必須實(shí)現(xiàn)的。

public class ConcreteTemplate extends AbstractTemplate {
    /**
     * 對(duì)父類基本方法的實(shí)現(xiàn)
     */
    protected void abstractMethod() {
        // TODO 業(yè)務(wù)相關(guān)的代碼
    }
    /**
     * 重寫父類的方法
     */
    @Override
    protected void hookMethod() {
        // TODO 業(yè)務(wù)相關(guān)的代碼
    }
}

模板方法模式的關(guān)鍵是:子類可以置換掉父類的可變部分,但是子類卻不可以改變模板方法所代表的頂級(jí)邏輯。

每當(dāng)定義一個(gè)新的子類時(shí),不要按照控制流程的思路去想,而應(yīng)當(dāng)按照“責(zé)任”的思路去想。換而言之,應(yīng)當(dāng)考慮哪些操作是必需置換掉的,哪些操作是可以置換掉的,以及哪些操作時(shí)不可以置換掉的。使用模板模式可以使這些責(zé)任變得清晰。

模板方法模式中的方法

模板方法模式中的方法可以分為兩大類:模板方法基本方法

模板方法

一個(gè)模板方法是定義在抽象類中的,把基本操作方法組合在一起形成一個(gè)總算法或者一個(gè)總行為的方法。

一個(gè)抽象類可以有任意多個(gè)模板方法,而不限于一個(gè)。每一個(gè)模板方法都可以調(diào)用任意多個(gè)具體方法。

基本方法

基本方法又可以分為三類:抽象方法(Abstract Method)具體方法(Concrete Method)鉤子方法(Hook Method)。

  • 抽象方法:一個(gè)抽象方法由抽象類聲明,由具體子類實(shí)現(xiàn)。在Java語(yǔ)言里抽象方法以Abstract關(guān)鍵字標(biāo)示。
  • 具體方法:一個(gè)具體方法由抽象類聲明并實(shí)現(xiàn),而子類并不實(shí)現(xiàn)或置換。
  • 鉤子方法:一個(gè)鉤子方法由抽象類聲明并實(shí)現(xiàn),而子類會(huì)加以拓展。通常抽象類給出的實(shí)現(xiàn)是一個(gè)空實(shí)現(xiàn),作為方法的默認(rèn)實(shí)現(xiàn)。

在上面的例子中,AbstractTemplate是一個(gè)抽象類,她有三個(gè)方法,其中abstractMethod()是一個(gè)抽象方法,它由抽象類聲明為抽象方法,并由子類實(shí)現(xiàn);hookMethod()是一個(gè)鉤子方法,它由抽象類聲明并提供默認(rèn)實(shí)現(xiàn),并且由子類置換掉;concreteMethod()是一個(gè)具體方法,它由抽象類聲明并實(shí)現(xiàn)。

默認(rèn)鉤子方法

一個(gè)鉤子方法常常由抽象類給出一個(gè)空實(shí)現(xiàn)作為此方法的默認(rèn)實(shí)現(xiàn)。這種空的鉤子方法叫做“Do Nothing Hook”。顯然,這種默認(rèn)鉤子方法在缺省適配模式里面已經(jīng)見過了,一個(gè)缺省適配模式講的是一個(gè)類為一個(gè)接口提供一個(gè)默認(rèn)的空實(shí)現(xiàn),從而使得缺省適配類的子類不必像實(shí)現(xiàn)接口那樣必須給出所有方法的實(shí)現(xiàn),因?yàn)橥ǔR粋€(gè)具體類并不需要所有的方法。

命名規(guī)則

命名規(guī)則是設(shè)計(jì)師之間可以溝通的管道之一,使用恰當(dāng)?shù)拿?guī)則可以幫助不同設(shè)計(jì)師之間的溝通。

鉤子方法的命名應(yīng)當(dāng)以do開始,這是熟悉設(shè)計(jì)模式的Java開發(fā)人員的標(biāo)準(zhǔn)做法。在上面的例子中,鉤子方法hookMethod()應(yīng)當(dāng)以do開頭;在HttpServlet類中,也遵從這一命名規(guī)則,例如doGet()doPost()等方法。

使用場(chǎng)景

考慮一個(gè)計(jì)算存款利息的例子。假設(shè)系統(tǒng)需要支持兩種存款賬號(hào),即貨幣市場(chǎng)(Money Market)賬號(hào)和定期存款(Certificate of Deposite)賬號(hào)。這兩種賬號(hào)的存款利息是不同的,因此,在計(jì)算 一個(gè)存戶的存款利息額時(shí),必須區(qū)分兩種不同的賬號(hào)類型。

這個(gè)系統(tǒng)的總行為應(yīng)當(dāng)是計(jì)算出利息,這就決定了作為一個(gè)模板方法模式的頂級(jí)邏輯應(yīng)當(dāng)是利息計(jì)算。由于利息計(jì)算涉及到兩個(gè)步驟: 一個(gè)基本方法給出賬號(hào)種類,另一個(gè)基本方法給出利息百分比。這兩個(gè)基本方法構(gòu)成具體邏輯,因?yàn)橘~號(hào)的類型不同,所以具體邏輯會(huì)有所不同。

顯然,系統(tǒng)需要一個(gè)抽象角色給出頂級(jí)行為的實(shí)現(xiàn),而將兩個(gè)作為細(xì)節(jié)步驟的基本方法留給具體子類實(shí)現(xiàn)。由于需要考慮的賬號(hào)有兩種:一種是貨幣市場(chǎng)賬號(hào),另一種是定期存款賬號(hào)。系統(tǒng)的類結(jié)構(gòu)如下圖所示:

存款利息計(jì)算結(jié)構(gòu)圖

示例代碼

抽象模板角色類

public abstract class Account {
    /**
     * 模板方法,計(jì)算利息數(shù)額
     * @return 返回利息數(shù)額
     */
    public final double calculateInterest() {
        //利息百分比
        double interestRate = doCalculateInterestRate();
        //賬號(hào)類型
        String accountType = doCalculateAccountType();
        //賬號(hào)內(nèi)存款
        double amount = calculateAmount(accountType);
        //返回利息數(shù)額
        return amount * interestRate;
    }
    /**
     * 基本方法,留給子類實(shí)現(xiàn)
     * @return
     */
    protected abstract String doCalculateAccountType();
    /**
     * 基本方法,留給子類實(shí)現(xiàn)
     * @return
     */
    protected abstract double doCalculateInterestRate();
    /**
     * 基本方法,已經(jīng)實(shí)現(xiàn)
     * @param accountType
     * @return
     */
    private double calculateAmount(String accountType) {
        //TODO 省略相關(guān)的業(yè)務(wù)邏輯
        return 1234.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("貨幣市場(chǎng)賬號(hào)的利息數(shù)額為:" + account.calculateInterest());
        account = new CDAccount();
        System.out.println("定期賬號(hào)的利息數(shù)額為:" + account.calculateInterest());
    }
}

模板方法模式在Servlet中的應(yīng)用

使用過Servlet的人都清楚,除了要在web.xml做相應(yīng)的配置之外,還需要繼承一個(gè)叫做HttpServlet的抽象類。HttpServlet類中,提供了一個(gè)service()方法,這個(gè)方法調(diào)用七個(gè)do方法中的一個(gè)或幾個(gè),完成對(duì)于客戶端調(diào)用的響應(yīng)。這些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) {
                    // 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);
        }
    }

當(dāng)然,這個(gè)service()方法也可以被子類置換掉。

下面給出一個(gè)簡(jiǎn)單的Servlet的例子:

簡(jiǎn)單Servlet例子

從上面的類圖可以看出,TestServlet類是HttpServlet類的子類,并且置換掉了父類的兩個(gè)方法: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");
    }
}

從上面的例子可以看出這是一個(gè)典型的模板方法模式。

HttpServlet擔(dān)任抽象模板角色

模板方法:由service()方法擔(dān)任。
基本方法:由doPost()doGet()等方法擔(dān)任。

TestServlet擔(dān)任具體模板角色

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

參考

《JAVA與模式》之模板方法模式

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,882評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,208評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,746評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,666評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,477評(píng)論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,960評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,047評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,200評(píng)論 0 288
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,726評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,617評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,807評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,327評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,049評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,425評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,674評(píng)論 1 281
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,432評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,769評(píng)論 2 372

推薦閱讀更多精彩內(nèi)容

  • 1 場(chǎng)景問題# 1.1 登錄控制## 幾乎所有的應(yīng)用系統(tǒng),都需要系統(tǒng)登錄控制的功能,有些系統(tǒng)甚至有多個(gè)登錄控制的功...
    七寸知架構(gòu)閱讀 1,992評(píng)論 3 53
  • 設(shè)計(jì)模式匯總 一、基礎(chǔ)知識(shí) 1. 設(shè)計(jì)模式概述 定義:設(shè)計(jì)模式(Design Pattern)是一套被反復(fù)使用、多...
    MinoyJet閱讀 3,957評(píng)論 1 15
  • 目錄 本文的結(jié)構(gòu)如下: 引言 什么是模板方法模式 模式的結(jié)構(gòu) 代碼示例 優(yōu)點(diǎn)和缺點(diǎn) 適用環(huán)境 模式應(yīng)用 一、引言 ...
    w1992wishes閱讀 817評(píng)論 0 3
  • 本篇文章介紹一種設(shè)計(jì)模式——外觀模式。本篇文章內(nèi)容參考:《JAVA與模式》之模板方法模式,模板方法模式深度解析(三...
    Ruheng閱讀 1,003評(píng)論 0 6
  • 當(dāng)我淚流滿面地看著八個(gè)月大的兒子時(shí)候,我從他閃閃發(fā)光的眼神里看到一股力量,他就在對(duì)我說,媽媽,別害怕,你可...
    素心5閱讀 251評(píng)論 0 1