說起“鉤子(Hook)”這個名詞,可能身為開發(fā)的同學大都聽說過谷炸。所謂Hook機制北专,是從Windows編程中流行開的一種技術(shù)。其主要思想是提前在可能增加功能的地方埋好(預(yù)設(shè))一個鉤子旬陡,這個鉤子并沒有實際的意義拓颓,當我們需要重新修改或者增加這個地方的邏輯的時候,把擴展的類或者方法掛載到這個點即可描孟。這可能不太好理解驶睦,接下來就開始以PHP為例講述下鉤子到底是什么。
講到“鉤子”匿醒,一定要提前說明的是一種設(shè)計模式场航,那就是行為型設(shè)計模式中的模板方法模式,明白了它的話廉羔,會讓我們更容易理解鉤子溉痢。
一.什么是模板方法模式
模板方法模式是一種基于繼承的代碼復(fù)用,它是一種類行為型模式憋他,在其結(jié)構(gòu)中只存在父類與子類之間的繼承關(guān)系孩饼。通過使用模板方法模式,可以將一些復(fù)雜流程的實現(xiàn)步驟封裝在一系列基本方法中竹挡,在抽象父類中提供一個稱之為模板方法的方法來定義這些基本方法的執(zhí)行次序镀娶,而通過其子類來實現(xiàn)某些步驟,從而使得相同的算法框架可以有不同的執(zhí)行結(jié)果揪罕。
簡單而言梯码,這個模式更像是進行婚禮活動(父類)宝泵,按照某一個安排的流程進行,只是每個不同的婚禮(子類)來說忍些,雖然流程是一樣的鲁猩,但是每個流程中,每個婚禮都有自己的玩法罢坝,例如結(jié)婚都要接親廓握,都要被伴娘攔住玩游戲,玩的是什么可能都不一樣嘁酿,然后接回家以后要進行一些禮節(jié)隙券,可能有的遞茶,有的吃餃子什么的各不相同闹司,以代碼的形式說明如下
abstract class AbstractWedding
{
// 定義婚禮順序
public function templateMethod()
{
$this->appendGame();
$this->appendMarriage();
}
// 婚禮的流程行為需要實現(xiàn)的
abstract protected function appendGame();
abstract protected function appendMarriage();
}
// 子類的婚禮進行實現(xiàn)
class Wedding1 extends AbstractWedding
{
protected function appendGame()
{
// 找鞋娱仔,喝酒
}
protected function appendMarriage()
{
// 教堂宣誓
}
}
class Wedding2 extends AbstractWedding
{
protected function appendGame()
{
// 發(fā)紅包
}
protected function appendMarriage()
{
// 親吻新娘
}
}
// 客戶
class Client
{
public function __construct()
{
$wedding = new Wedding1();
$wedding->templateMethod();
}
}
要注意的是,在處理的過程中游桩,要遵循反向控制接口(“好萊塢原則”)牲迫,這個原則是指父類調(diào)用子類的操作,而子類不調(diào)用父類的操作借卧。好萊塢原則于模版方法設(shè)計模式緊密相關(guān)盹憎,因為它在父類中實現(xiàn),除了templateMethod方法外铐刘,父類的其他方法都是抽象和受保護的方法陪每。所以,盡管客戶實例化一個具體類镰吵,但是它調(diào)用了父類中實現(xiàn)的方法檩禾。
二.什么時候使用模板方法
如果已經(jīng)明確算法中的一些步驟,不過這些步驟可以采用多種不同的方法進行實現(xiàn)疤祭,就可以使用模板方法模式盼产。如果算法的步驟不變,可以把這些步驟交給子類去實現(xiàn)勺馆。這種情況下辆飘,可以使用設(shè)計模式來組織抽象類中的基本操作,然后子類去做這些基本操作所需要的具體過程谓传。
還有一種用法可能稍微復(fù)雜一些,可能需要把子類共同的行為放在一個類里面芹关,以避免代碼重復(fù)续挟,畢竟每次實現(xiàn)的子類不回是完全不同的,估計很快就會產(chǎn)生重復(fù)代碼侥衬。
最后一點诗祸,就是可以使用模板方法模式來控制子類拓展跑芳,也就是我們本次需要說的鉤子≈甭可以利用鉤子控制拓展博个,只在鉤子操作所在的某些位置允許拓展。
三.模板方法設(shè)計模式中的鉤子
有的時候功偿,模板方法函數(shù)中可能有一個不想要的步驟盆佣,例如,在我們購買商品的時候要計算最終價格械荷,商品價格+運費+服務(wù)產(chǎn)生費用共耍,不過在有些活動中,顧客商品價格滿100元就可以免運費吨瞎。這里就要使用到模板方法的鉤子痹兜。
也就是說,在模板方法設(shè)計模式中颤诀,利用鉤子可以將一個方法作為模板方法的一部分字旭,不過不一定會用到這個方法。換句話說崖叫,它是方法的一部分遗淳,不過它包含一個鉤子,可以處理例外的情況归露,子類可以為算法增加一個可選元素洲脂,這樣以來,盡管仍然按照父類模板方法建立的順序執(zhí)行剧包,但是有可能并不完全按照模板方法期望的那樣動作恐锦。
不過這可能有點違背之前說的需要遵守的“好萊塢原則”,因為子類沒有遵循父類設(shè)置的順序疆液,好萊塢原則要求只有父類能夠改變框架一铅。鉤子的話更像一個“后門”,進行處理例外的情況堕油。
舉個鉤子的簡單的例子潘飘,就拿免運費的例子進行實現(xiàn)。注意掉缺,雖然子類可以改變鉤子的行為卜录,但是仍然要遵守模板方法中定義的執(zhí)行順序。
// 建立鉤子
abstract class IHook
{
protected $purchased;
protected $hookSpecial;
protected $shippingHook;
protected $fullCost;
public function templateMethod($total, $special)
{
$this->purchased = $total;
$this->hookSpecial = $special;
$this->addTax();
$this->addShippingHook();
$this->cost();
}
protected function addTax();
protected function addShippingHook();
protected function cost();
}
抽象類IHook定義了幾個抽象方法眶明,并且確定了他們的執(zhí)行順序艰毒,這里hook方法放到了中間,實際上它可以放在順序中的任意位置搜囱。$special代表的是是否免運費丑瞧。
// 實現(xiàn)鉤子
class Calc extends IHook
{
protected function addTax()
{
$this->cost = $this->purchased + ($this->purchased * 0.07);
}
protected function addShippingHook()
{
if(!$this->hookSpecial) {
// 這里設(shè)置變量為了更好理解柑土,其實按照題意應(yīng)該是 if($this->cost > 100)
$this->cost += 5;
}
}
protected function cost()
{
return $this->cost;
}
}
addTax()和cost()都是標準方法,只有一個實現(xiàn)绊汹,不過addShippingHook()的實現(xiàn)有所不同稽屏,其中有一個條件來確定是否要增加運費,這個就是鉤子西乖。 客戶 Client類具體使用不做說明了狐榔。其實就是一個設(shè)置和調(diào)用了。
四.優(yōu)缺點
優(yōu)點
1.提高代碼復(fù)用性 浴栽,將相同部分的代碼放在抽象的父類中
2.提高了拓展性 荒叼,將不同的代碼放入不同的子類中,通過對子類的擴展增加新的行為
3.實現(xiàn)了反向控制典鸡,通過一個父類調(diào)用其子類的操作被廓,通過對子類的擴展增加新的行為,實現(xiàn)了反向控制 & 符合“開閉原則”
缺點
引入了抽象類萝玷,每一個不同的實現(xiàn)都需要一個子類來實現(xiàn)嫁乘,導(dǎo)致類的個數(shù)增加,從而增加了系統(tǒng)實現(xiàn)的復(fù)雜度球碉。
你理解中的“鉤子”是什么樣的呢蜓斧?