模板方法模式在乙個方法中定義乙個演算法的骨架,而將一些步驟延遲到子類中。模板方法可以在不改變演算法結構的情況下,重新定義演算法中的某些步驟。
這個定義還是比較好理解的,由父類定義定義乙個模板,這裡的模板就是乙個方法,它將演算法定義成一組步驟,其中任何步驟都可以是抽象的,由子類負責實現。通過模板方法模式,可以確保演算法的結構保持不變,同時由子類提供部分實現。下面我將通過具體例項來講解一下模板方法模式。
假設我們需要模擬飲品店準備飲品的過程,例如在準備咖啡時,分為將水煮沸、用沸水沖泡咖啡、將咖啡倒入杯子、新增糖和牛奶四個步驟;準備茶時,分為將水煮沸、用沸水浸泡茶葉、把茶倒入杯子、新增檸檬四個步驟。
第一眼看到需求,我們很容易就可以想到,要模擬準備飲品的過程,我們只需要針對每種飲品定義乙個類,在每個類中編寫準備的過程即可,所以我們可以分別定義咖啡類coffee和茶類tea,它們的實現方式如下:
/**咖啡類*/
public
class
coffee
public
void
boilwater()
public
void
brewcoffeegrinds()
public
void
pourincup()
public
void
addsugarandmilk()
}//-------------------------------------------
/**茶類*/
public
class
teapublic
void
boilwater()
public
void
steepteabag()
public
void
pourincup()
public
void
addlemon()
}
在這兩個類中我們分別定義了乙個準備飲品的方法,這個方法中包含了準備飲品的每個步驟,並各自實現了所有的步驟,按照這個思路,以後如果有新的飲品出現,我們只需要定義新的飲品類即可。但是仔細觀察我們定義的兩個類可以發現,這兩個類都有準備飲品的方法,我們是否可以把它抽象出來?另外,準備飲品的步驟中,咖啡和茶的第一步均為「將水煮沸」、第三步均為「將xx倒進杯子」,這兩個步驟是一樣的,我們是否也能將它們抽出來呢?
依照物件導向的程式設計思想,我們可以將飲品類抽象出乙個父類,這個父類包含乙個準備飲品的抽象方法(因為所有飲品都必須實現這個方法,但是實現方式不一定相同)和所有飲品的公共方法,我們可以按照如下的方式進行定義:
/**所有咖啡因飲料的公共父類*/
public
abstract
class
caffeinebeverage
public
void
pourincup()
}
有了這個父類之後我們可以定義它的子類coffee和tea:
/**咖啡類*/
public
class
coffee
extends
caffeinebeverage
public
void
brewcoffeegrinds()
public
void
addsugarandmilk()
}//-------------------------------------------
/**茶類*/
public
class
teaextends
caffeinebeverage
public
void
steepteabag()
public
void
addlemon()
}
這兩個類分別實現了準備飲品方法,同時還自己定義了兩個獨有的方法,在準備飲品方法中,它們同時呼叫了父類和子類的步驟。
通過抽象和繼承能夠我們很好的實現需求,但是有沒有辦法再進行優化呢?我們繼續觀察咖啡類和茶類,這兩個類自行定義了兩個方法,這兩個方法是相互不同的,但是通過觀察我們發現,沖泡咖啡和浸泡茶葉是不是可以合併成乙個方法,例如加入原料這樣的方法呢?最後乙個方法是加糖和牛奶或者加檸檬,是否也可以合併成乙個方法,例如加入輔料這樣的方法呢?這樣我們可以將準備飲品的所有的步驟都抽象到父類中了,父類可以直接定義乙個不可變的準備飲品方法,在這個方法中定義一系列的步驟,子類無需實現這個方法,需要準備飲品時直接呼叫父類的方法即可,但是子類可以自行實現其中的一些步驟,這樣可以滿足自己的特殊需求。我們可以通過模板方法模式來實現需求。
我們修改父類的定義:
public
abstract
class
caffeinebeverage
}public
void
boilwater()
public
void
pourincup()
public
abstract
void
brew()
;public
abstract
void
addcondiments()
;public
boolean
customerwantscondiments()
}
我們先看一下這個類的定義,我們首先定義了乙個準備飲品的方法preparerecipe(),這個方法中呼叫了準備飲品的一系列步驟,同時我們將preparerecipe()方法定義為final,這樣子類就不能覆蓋這個方法了。然後我們在父類中定義了所有準備飲品的步驟,這些步驟有些是已經實現了的,有些步驟未實現,未實現的步驟可由子類自行實現。另外,我們還定義了乙個方法customerwantscondiments(),這個方法返回的是乙個布林值,在這個類中我們將這個方法的返回定義為恆定的值true,這個方法的使用我們會在後續進行詳細說明。
接下來我們定義咖啡類和茶類:
/**咖啡類*/
public
class
coffee
extends
caffeinebeverage
@override
public
void
addcondiments()
public
boolean
customerwantscondiments()
else
}private string getuserinput()
catch
(ioexception e)
if(answer == null)
return answer;}}
//---------------------------------
/**茶類*/
public
class
teaextends
caffeinebeverage
@override
public
void
addcondiments()
}
可以看到,咖啡類和茶類都實現了brew()方法和addcondiments()方法,但是咖啡類還重寫了customerwantscondiments()方法,這個方法用於根據使用者輸入判斷是否需要新增輔料,根據父類中的步驟定義,只有使用者需要新增輔料的時候才會呼叫新增輔料方法,但是茶類中並沒有重寫這個方法,也就是說,茶類使用的是父類定義的customerwantscondiments()方法,也就是永遠返回true,這樣一來,茶類在準備飲品的時候會一直新增檸檬。在模板方法模式中,customerwantscondiments()這樣的方法被稱為鉤子,它在父類定義中通常什麼都不做,或者有乙個預設的實現,子類可以覆蓋這個方法,也可以不覆蓋。
在模板方法模式中,我們將需要子類實現的步驟定義為抽象方法,鉤子也是需要子類實現的,那麼我們什麼時候使用抽象方法什麼時候使用鉤子呢?當子類必須要實現演算法中某個步驟的時候,就可以使用抽象方法,當某個步驟是可選的,那麼就可以使用鉤子。
那麼我們為什麼要使用鉤子呢?鉤子可以讓子類實現演算法中的可選部分,同時鉤子能讓子類有機會對模板方法中某些即將發生的(或剛剛發生的)步驟做出反應,另外,鉤子也讓子類有能力為其抽象類做出一些決定,就像我們上面示例中,我們通過鉤子來讓使用者決定是否需要新增輔料。
最後,我們來看一下模板方法模式的類圖(類圖摘自《head first設計模式》):
父類中包含了乙個模板方法,這個模板方法實現了演算法的過程,但是演算法的步驟父類並沒有全部實現,可以由子類來實現乙個或多個步驟。
模板方法模式定義了演算法的步驟,並將這些步驟的實現延遲到了子類,它為我們提供了乙個**復用的重要技巧。在模板方法模式的抽象類中,我們可以定義具體方法、抽象方法和鉤子,其中抽象方法需要由子類實現,子類可以選擇修改或者不修改鉤子。模板方法模式在實際中應用很多,而且很多時候我們會遇到模板方法的變體,這就需要我們自己有能力進行分析辨別。
模板設計模式 設計模式 模板方法模式
在模板模式 template pattern 中,乙個抽象類公開定義了執行它的方法的方式 模板。它的子類可以按需要重寫方法實現,但呼叫將以抽象類中定義的方式進行。這種型別的設計模式屬於行為型模式。首先需要一定抽象的定義,沒有具體的實現,但是在抽象類的行為中,子類去程序這個抽象類,重寫抽象方法,實現不...
設計模式 模板方法設計模式
物件導向,萬物皆物件,用乙個雷來反應現實生活中的東西。比如銀行系統,業務公升級 活期 定期,差別很小,就加判斷 違背單一職責 差別比較多,尤其是模擬較複雜,型別拆分下。拆分之後,自然就有父類,重用。利率 每個客戶端都有利率,但是各不一樣 抽象方法。show 不分客戶端是一樣的,個別客戶端是不一樣的 ...
設計模式 模板方法模式
模板方法模式 類庫中大量使用,例如idbconnection介面 dbconnection抽象類 派生的sqlconnection和派生的oledbconnection就是使用了這種方法 1。介面,到能做的定義進來。一種規範 2.把共同的部分進去分離出來,放到乙個抽象的父類去實現.3.子類中實現 不...