模板模式
如果說“用到了接口-實(shí)現(xiàn)類,就用到了策略模式”,那么策略模式可能是我們無(wú)意之間用得最多的設(shè)計(jì)模式。如果排除“無(wú)意中”的使用,只考慮有意識(shí)、有目的情況,我想,模板模式應(yīng)該算得上“使用最多的設(shè)計(jì)模式”,應(yīng)該也是大多數(shù)人第一個(gè)運(yùn)用到實(shí)踐中的設(shè)計(jì)模式。
有意和無(wú)意,有很大差別。
我想,其中的主要原因在于我們大多數(shù)人開發(fā)的都是業(yè)務(wù)系統(tǒng)、業(yè)務(wù)流程。業(yè)務(wù)系統(tǒng)和流程有一個(gè)顯著特點(diǎn):在一套基礎(chǔ)業(yè)務(wù)流程上,改一改這一步、調(diào)一調(diào)那一步,就得到了所謂新業(yè)務(wù)、新流程、新需求。模板模式與這一特點(diǎn)一拍即合,自然就很容易進(jìn)入到開發(fā)實(shí)踐中。
是什么
關(guān)于模板模式的定義,我在網(wǎng)上查到的了很多這樣描述的:
模板模式在一個(gè)抽象類中定義了一個(gè)算法的骨架,而將一些步驟延遲到子類中。
這個(gè)定義里,其它部分都好說,就是“延遲”二字讓人費(fèi)解。延遲一般是指“比xxx更慢/更晚”。在模板模式里,是誰(shuí)比誰(shuí)更慢或更晚呢?
看了一圈,我比較喜歡這個(gè)定義:
The Template Method design pattern is a behavioral design pattern that defines the skeleton of an algorithm in a superclass but allows subclasses to override specific steps of the algorithm without changing its structure. 模板方法模式是一種行為設(shè)計(jì)模式。它在父類中好定義算法骨架,并允許子類在不修改算法結(jié)構(gòu)的情況下,重寫其中的特定的步驟。
從這個(gè)定義中,我們可以找出模板模式的四個(gè)關(guān)鍵要素:父類、子類、算法骨架、被重寫的步驟,如下圖所示:
父類之于子類有很多種意義。在模板模式中,父類的主要意義在于定義算法骨架。
所謂“算法骨架”,首先是操作流程,其次則是“可重寫的步驟”。有了“可重寫的步驟”,子類才能夠“重寫”這些步驟。但也因?yàn)椴僮髁鞒淘诟割愔卸x好了,因而子類只能重寫個(gè)別步驟的具體實(shí)現(xiàn),而不能修改算法的完整流程、步驟順序等。
網(wǎng)上提到模板模式,大多強(qiáng)調(diào)父類一定是抽象類。其實(shí)未必。盡管不太符合里氏替換原則,模板模式中的父類也可以是普通類。這是后話。
所謂“可重寫步驟”,通常即封裝有部分操作流程的方法。有些時(shí)候,它們也叫“鉤子方法”。子類通過重寫這些方法,來(lái)改寫操作流程中的部分細(xì)節(jié)。這些方法的可見性、入?yún)ⅰ⒎祷刂?,以及“顆粒度”等,都是值得慎重考慮的方面。
在這些方面中,可見性也許是其中最簡(jiǎn)單的一個(gè)。為了兼容跨包的子類,父類的可見性一般都是public。但方法的可見性則不宜過大。有些代碼里一股腦地將它們也定義為public,也有些開發(fā)者把這些方法放到了接口上,實(shí)在沒有必要。
這些方法只服務(wù)于兩個(gè)目標(biāo):組成完整的操作流程,以及供子類重寫。因此,保證這些方法能被子類訪問到即可,其它調(diào)用者一律禁止入內(nèi)。
一個(gè)方法的可擴(kuò)展性,受方法入?yún)⒂绊懞艽?。我見過不少子類A要用參數(shù)ABC、而子類B要用參數(shù)CDE的。如果方法入?yún)⒍x不當(dāng),從子類一擴(kuò)展到子類二時(shí),就免不了一番傷筋動(dòng)骨。
怎么做
// 父類,定義算法的骨架
public class ParentClass {
// 定義算法的步驟
public final Result templateMethod(Param p) {
Temp1 temp1 = step1(p);
Temp2 temp2 = step2(p, temp1);
Result result = step3(temp1, temp2);
return result;
}
// 定義算法的第一步
protected Temp1 step1(Param p){
// 默認(rèn)實(shí)現(xiàn),略
}
// 定義算法的第二步
protected Temp2 step2(Param p, Temp1 temp1){
// 默認(rèn)實(shí)現(xiàn),略
}
// 定義算法的第三步
protected Result step3(Temp1 temp1,Temp2 temp2){
// 默認(rèn)實(shí)現(xiàn),略
}
}
// 具體子類,實(shí)現(xiàn)算法的步驟
class ConcreteClassA extends ParentClass {
@Override
protected Temp1 step1(Param p){
// 修改實(shí)現(xiàn)步驟1,略
}
@Override
protected Result step3(Temp1 temp1,Temp2 temp2){
// 修改實(shí)現(xiàn)步驟3,略
}
}
// 具體子類,實(shí)現(xiàn)算法的步驟
class ConcreteClassB extends ParentClass {
@Override
protected Temp2 step2(Param p, Temp1 temp1){
// 修改實(shí)現(xiàn)步驟2,略
}
}
// 使用模板模式
public class Client {
public static void main(String[] args) {
ParentClass template = new ConcreteClassA();
template.templateMethod();
template = new ConcreteClassB();
template.templateMethod();
}
}
一般來(lái)說,拆分方法步驟有兩種思路。第一種是在還沒有子類的時(shí)候,就在父類中設(shè)計(jì)好方法步驟,并預(yù)留下供子類重寫的方法。第二種則是沒有子類時(shí),父類不做擴(kuò)展性考慮;在必要時(shí),再根據(jù)子類的擴(kuò)展需求,在父類中拆分方法步驟、設(shè)計(jì)可重寫方法。
這兩種方法,和“自上而下”和“自下而上”的方法論頗有些異曲同工。它們的優(yōu)缺點(diǎn)也可以從類似的角度來(lái)分析。
第一種方法,“自上而下”地設(shè)計(jì)好一個(gè)模板類,為子類預(yù)留下擴(kuò)展空間。如果父類的流程有明確的步驟,或者子類的擴(kuò)展有清晰的方向,那么借助這種自上而下的方法,我們可以設(shè)計(jì)出一套卓有成效的父子類來(lái)。
然而,如果沒有這兩個(gè)前提——“父類的流程有明確的步驟”,或者“子類的擴(kuò)展有清晰的方向”——自上而下的方法就很容易變成過度設(shè)計(jì),最終得到南轅北轍的效果。
第二種方法則是“自下而上”地演化出一套模板和父子類。它很好地彌補(bǔ)了第一種方法的缺陷,但也有明顯的問題。
“按需演化”很容易演變成“按需特化”,得到的模板不倫不類,既復(fù)雜難懂、又難以擴(kuò)展。
我個(gè)人更傾向于從上而下的方法論。條件允許時(shí),應(yīng)該優(yōu)先考慮自上而下地設(shè)計(jì),用優(yōu)秀的“頂層設(shè)計(jì)”來(lái)指導(dǎo)、推動(dòng)底層實(shí)現(xiàn)。如果從自下而上入手,也應(yīng)注意審時(shí)度勢(shì),在必要的時(shí)候梳理頂層設(shè)計(jì),優(yōu)化模板類結(jié)構(gòu)。
無(wú)論自上而下還是自下而上,都是一件?費(fèi)時(shí)費(fèi)心的工作。我們?yōu)槭裁匆ㄙM(fèi)時(shí)間精力來(lái)設(shè)計(jì)和使用模板模式呢?模板模式有怎樣的優(yōu)點(diǎn),?又有哪些缺點(diǎn)呢?
?
為什么
模板模式可以提高代碼復(fù)用率和可擴(kuò)展性。這大概?是它最顯而易見的兩個(gè)優(yōu)點(diǎn),這里就不啰嗦了。
?
模板模式還有一個(gè)更高層次的優(yōu)點(diǎn):它是一種入門級(jí)的?建模方式。
?
個(gè)人理解,?建模需要抓住事物的核心本質(zhì),并簡(jiǎn)練而明確地描述其組成元素和各元素的組織結(jié)構(gòu)、運(yùn)轉(zhuǎn)規(guī)律。
設(shè)計(jì)模板模式正是這樣一個(gè)過程。我們先要分析業(yè)務(wù)流程,然后把流程按一定順序分解成若干步驟,識(shí)別各步驟的入?yún)⒑头祷刂?,還要把這些步驟區(qū)分為“所有子類通用的”和“允許子類修改的”。完成這些工作之后,我們才能確定父類如何實(shí)現(xiàn)、子類如何重寫。
在這個(gè)過程中,我們得到的步驟、入?yún)⒑头祷刂担鋵?shí)就是這個(gè)流程模型中的“元素”;各步驟的執(zhí)行順序、入?yún)⒑头祷刂档霓D(zhuǎn)換和傳遞,就是這個(gè)模型的運(yùn)轉(zhuǎn)規(guī)律;步驟的分類則可以理解為這個(gè)模型的組織結(jié)構(gòu)的基礎(chǔ):我們需要借助“允許子類修改的步驟”,來(lái)組織父子類結(jié)構(gòu)。
你看,一個(gè)流程模型就這樣拔地而起了。
當(dāng)然,建模沒有這么簡(jiǎn)單, 建好模更不容易。不過,用模型思維來(lái)分析和構(gòu)建系統(tǒng),于系統(tǒng)、于自己都百利一弊——唯一的弊端在于成本偏高。
模板模式作為一種入門級(jí)的建模方式,就像是一扇通往模型思維的大門。當(dāng)我們有意識(shí)、有目的地使用模板模式時(shí),其實(shí)就已經(jīng)站到了這扇門前。推開它、走進(jìn)去,我們會(huì)發(fā)現(xiàn)更廣闊的天地。
推開大門和掉頭走開,有很大差別。
模型的7大用途(REDCAPE)
推理:識(shí)別條件并推斷邏輯含義。
解釋:為經(jīng)驗(yàn)現(xiàn)象提供(可檢驗(yàn)的)解釋。
設(shè)計(jì):選擇制度、政策和規(guī)則的特征。
溝通:將知識(shí)與理解聯(lián)系起來(lái)。
行動(dòng):指導(dǎo)政策選擇和戰(zhàn)略行動(dòng)。
預(yù)測(cè):對(duì)未來(lái)和未知現(xiàn)象進(jìn)行數(shù)值和分類預(yù)測(cè)。
探索:分析探索可能性和假說。
《模型思維》
總的來(lái)說,模板模式在需要重用業(yè)務(wù)流程、同時(shí)允許子類重寫某些步驟的場(chǎng)景中非常有用。
然而,在選擇使用模板模式時(shí),我們需要權(quán)衡其帶來(lái)的代碼復(fù)用和行為一致性的優(yōu)點(diǎn),以及可能增加的類數(shù)量和復(fù)雜性的缺點(diǎn),審慎使用。