設(shè)計(jì)模式——模板方法模式
模板方法模式
亦稱:Template Method
意圖
模板方法模式是一種行為設(shè)計(jì)模式, 它在超類中定義了一個(gè)算法的框架, 允許子類在不修改結(jié)構(gòu)的情況下重寫算法的特定步驟。

問題
假如你正在開發(fā)一款分析公司文檔的數(shù)據(jù)挖掘程序。用戶需要向程序輸入各種格式 (PDF、 DOC 或 CSV) 的文檔, 程序則會(huì)試圖從這些文件中抽取有意義的數(shù)據(jù), 并以統(tǒng)一的格式將其返回給用戶。
該程序的首個(gè)版本僅支持 DOC 文件。在接下來的一個(gè)版本中, 程序能夠支持 CSV 文件。一個(gè)月后, 你 “教會(huì)” 了程序從 PDF 文件中抽取數(shù)據(jù)。

數(shù)據(jù)挖掘類中包含許多重復(fù)代碼。
一段時(shí)間后, 你發(fā)現(xiàn)這三個(gè)類中包含許多相似代碼。盡管這些類處理不同數(shù)據(jù)格式的代碼完全不同, 但數(shù)據(jù)處理和分析的代碼卻幾乎完全一樣。如果能在保持算法結(jié)構(gòu)完整的情況下去除重復(fù)代碼, 這難道不是一件很棒的事情嗎?
還有另一個(gè)與使用這些類的客戶端代碼相關(guān)的問題:客戶端代碼中包含許多條件語句, 以根據(jù)不同的處理對(duì)象類型選擇合適的處理過程。如果所有處理數(shù)據(jù)的類都擁有相同的接口或基類, 那么你就可以去除客戶端代碼中的條件語句, 轉(zhuǎn)而使用多態(tài)機(jī)制來在處理對(duì)象上調(diào)用函數(shù)。
解決方案
模板方法模式建議將算法分解為一系列步驟, 然后將這些步驟改寫為方法, 最后在 “模板方法” 中依次調(diào)用這些方法。步驟可以是 抽象的, 也可以有一些默認(rèn)的實(shí)現(xiàn)。為了能夠使用算法, 客戶端需要自行提供子類并實(shí)現(xiàn)所有的抽象步驟。如有必要還需重寫一些步驟 (但這一步中不包括模板方法自身)。
讓我們考慮如何在數(shù)據(jù)挖掘應(yīng)用中實(shí)現(xiàn)上述方案。我們可為圖中的三個(gè)解析算法創(chuàng)建一個(gè)基類, 該類將定義調(diào)用了一系列不同文檔處理步驟的模板方法。

模板方法將算法分解為步驟, 并允許子類重寫這些步驟, 而非重寫實(shí)際的模板方法。
首先, 我們將所有步驟聲明為 抽象類型, 強(qiáng)制要求子類自行實(shí)現(xiàn)這些方法。在我們的例子中, 子類中已有所有必要的實(shí)現(xiàn), 因此我們只需調(diào)整這些方法的簽名, 使之與超類的方法匹配即可。
現(xiàn)在, 讓我們看看如何去除重復(fù)代碼。對(duì)于不同的數(shù)據(jù)格式, 打開和關(guān)閉文件以及抽取和解析數(shù)據(jù)的代碼都不同, 因此無需修改這些方法。但分析原始數(shù)據(jù)和生成報(bào)告等其他步驟的實(shí)現(xiàn)方式非常相似, 因此可將其提取到基類中, 以讓子類共享這些代碼。
正如你所看到的那樣, 我們有兩種類型的步驟:
? 抽象步驟必須由各個(gè)子類來實(shí)現(xiàn)
? 可選步驟已有一些默認(rèn)實(shí)現(xiàn), 但仍可在需要時(shí)進(jìn)行重寫
還有另一種名為鉤子的步驟。 鉤子是內(nèi)容為空的可選步驟。即使不重寫鉤子, 模板方法也能工作。鉤子通常放置在算法重要步驟的前后, 為子類提供額外的算法擴(kuò)展點(diǎn)。
真實(shí)世界類比

可對(duì)典型的建筑方案進(jìn)行微調(diào)以更好地滿足客戶需求。
模板方法可用于建造大量房屋。標(biāo)準(zhǔn)房屋建造方案中可提供幾個(gè)擴(kuò)展點(diǎn), 允許潛在房屋業(yè)主調(diào)整成品房屋的部分細(xì)節(jié)。
每個(gè)建造步驟 (例如打地基、 建造框架、 建造墻壁和安裝水電管線等) 都能進(jìn)行微調(diào), 這使得成品房屋會(huì)略有不同。
模板方法模式結(jié)構(gòu)
1. 抽象類 (Abstract-Class) 會(huì)聲明作為算法步驟的方法, 以及依次調(diào)用它們的實(shí)際模板方法。算法步驟可以被聲明為
抽象類型, 也可以提供一些默認(rèn)實(shí)現(xiàn)。2. 具體類 (Con-crete-Class) 可以重寫所有步驟, 但不能重寫模板方法自身。
偽代碼
本例中的模板方法模式為一款簡單策略游戲中人工智能的不同分支提供 “框架”。
模板方法模式示例的結(jié)構(gòu)
一款簡單游戲的 AI 類。
游戲中所有的種族都有幾乎同類的單位和建筑。因此你可以在不同的種族上復(fù)用相同的 AI 結(jié)構(gòu), 同時(shí)還需要具備重寫一些細(xì)節(jié)的能力。通過這種方式, 你可以重寫半獸人的 AI 使其更富攻擊性, 也可以讓人類側(cè)重防守, 還可以禁止怪物建造建筑。在游戲中新增種族需要?jiǎng)?chuàng)建新的 AI 子類, 還需要重寫 AI 基類中所聲明的默認(rèn)方法。
// 抽象類定義了一個(gè)模板方法,其中通常會(huì)包含某個(gè)由抽象原語操作調(diào)用組成的算// 法框架。具體子類會(huì)實(shí)現(xiàn)這些操作,但是不會(huì)對(duì)模板方法做出修改。class GameAI is// 模板方法定義了某個(gè)算法的框架。method turn() iscollectResources()buildStructures()buildUnits()attack()// 某些步驟可在基類中直接實(shí)現(xiàn)。method collectResources() isforeach (s in this.builtStructures) dos.collect()// 某些可定義為抽象類型。abstract method buildStructures()abstract method buildUnits()// 一個(gè)類可包含多個(gè)模板方法。method attack() isenemy = closestEnemy()if (enemy == null)sendScouts(map.center)elsesendWarriors(enemy.position)abstract method sendScouts(position)abstract method sendWarriors(position)// 具體類必須實(shí)現(xiàn)基類中的所有抽象操作,但是它們不能重寫模板方法自身。class OrcsAI extends GameAI ismethod buildStructures() isif (there are some resources) then// 建造農(nóng)場(chǎng),接著是谷倉,然后是要塞。method buildUnits() isif (there are plenty of resources) thenif (there are no scouts)// 建造苦工,將其加入偵查編組。else// 建造獸族步兵,將其加入戰(zhàn)士編組。// ...method sendScouts(position) isif (scouts.length > 0) then// 將偵查編組送到指定位置。method sendWarriors(position) isif (warriors.length > 5) then// 將戰(zhàn)斗編組送到指定位置。// 子類可以重寫部分默認(rèn)的操作。class MonstersAI extends GameAI ismethod collectResources() is// 怪物不會(huì)采集資源。method buildStructures() is// 怪物不會(huì)建造建筑。method buildUnits() is// 怪物不會(huì)建造單位。
模板方法模式適合應(yīng)用場(chǎng)景
當(dāng)你只希望客戶端擴(kuò)展某個(gè)特定算法步驟, 而不是整個(gè)算法或其結(jié)構(gòu)時(shí), 可使用模板方法模式。
模板方法將整個(gè)算法轉(zhuǎn)換為一系列獨(dú)立的步驟, 以便子類能對(duì)其進(jìn)行擴(kuò)展, 同時(shí)還可讓超類中所定義的結(jié)構(gòu)保持完整。
當(dāng)多個(gè)類的算法除一些細(xì)微不同之外幾乎完全一樣時(shí), 你可使用該模式。但其后果就是, 只要算法發(fā)生變化, 你就可能需要修改所有的類。
在將算法轉(zhuǎn)換為模板方法時(shí), 你可將相似的實(shí)現(xiàn)步驟提取到超類中以去除重復(fù)代碼。子類間各不同的代碼可繼續(xù)保留在子類中。
實(shí)現(xiàn)方式
1. 分析目標(biāo)算法, 確定能否將其分解為多個(gè)步驟。從所有子類的角度出發(fā), 考慮哪些步驟能夠通用, 哪些步驟各不相同。
2. 創(chuàng)建抽象基類并聲明一個(gè)模板方法和代表算法步驟的一系列抽象方法。在模板方法中根據(jù)算法結(jié)構(gòu)依次調(diào)用相應(yīng)步驟??捎?nbsp;
final最終修飾模板方法以防止子類對(duì)其進(jìn)行重寫。3. 雖然可將所有步驟全都設(shè)為抽象類型, 但默認(rèn)實(shí)現(xiàn)可能會(huì)給部分步驟帶來好處, 因?yàn)樽宇悷o需實(shí)現(xiàn)那些方法。
4. 可考慮在算法的關(guān)鍵步驟之間添加鉤子。
5. 為每個(gè)算法變體新建一個(gè)具體子類, 它必須實(shí)現(xiàn)所有的抽象步驟, 也可以重寫部分可選步驟。
模板方法模式優(yōu)缺點(diǎn)
? 你可僅允許客戶端重寫一個(gè)大型算法中的特定部分, 使得算法其他部分修改對(duì)其所造成的影響減小。
? 你可將重復(fù)代碼提取到一個(gè)超類中。
? 部分客戶端可能會(huì)受到算法框架的限制。
? 通過子類抑制默認(rèn)步驟實(shí)現(xiàn)可能會(huì)導(dǎo)致違反里氏替換原則。
? 模板方法中的步驟越多, 其維護(hù)工作就可能會(huì)越困難。
與其他模式的關(guān)系
? 工廠方法模式是模板方法模式的一種特殊形式。同時(shí), 工廠方法可以作為一個(gè)大型模板方法中的一個(gè)步驟。
? 模板方法基于繼承機(jī)制:它允許你通過擴(kuò)展子類中的部分內(nèi)容來改變部分算法。 策略模式基于組合機(jī)制:你可以通過對(duì)相應(yīng)行為提供不同的策略來改變對(duì)象的部分行為。 模板方法在類層次上運(yùn)作, 因此它是靜態(tài)的。 策略在對(duì)象層次上運(yùn)作, 因此允許在運(yùn)行時(shí)切換行為。

入骨相思知不知
玲瓏骰子安紅豆



入我相思門,知我相思苦,長相思兮長相憶,短相思兮無窮極。



