設計模式 ——— 模板方法模式

weixin_33866037發表於2017-11-11

TEMPLATE METHOD(模板方法) ———— 類行為型模式

意圖

定義一個操作中的演算法骨架,而將一些步驟延遲到子類中。TemplateMethod使得子類可以不改變一個演算法的結構即可重定義該演算法的某些特定步驟。

適用性

  • 需要固定定義演算法骨架,實現一個演算法的不變的部分,並把可變的行為留給子類來實現的情況;
  • 各個子類中具有公共行為,應該抽取出來,集中在一個公共類中去實現,從而避免程式碼重複;
  • 需要控制子類擴充套件的情況。模板方法模式會在特定的點來呼叫“hook”操作,這樣只允許在這些點進行擴充套件;

結構

4235178-d5f7c373c757b575.png
模板方法模式結構圖
  • AbstractClass(抽象類)
    定義抽象的原語操作(primitive operation),具體的子類將重定義它們以實現一個演算法的各步驟。
    實現一個模板方法,定義一個演算法的骨架。該模板方法不僅呼叫原語操作,也呼叫定義在AbstractClass或其他物件中的操作。
  • ConcreteClass(具體類)
    實現原語操作以完成演算法中與特定子類相關的步驟。

認識模板方法模式

變與不變

程式設計的一個很重要的思考點就是“變與不變”,也就是分析程式中哪些功能是可變的,哪些功能是不變的,然後把不變的部分抽象出來,進行公共的實現,把變化的部分分離出去,用介面來封裝隔離,或者是用抽象類來約束子類行為。

模板方法模式很好的體現了這一點。模板類實現的就是不變的方法和演算法的骨架,而需要變化的地方,都通過抽象方法,把具體實現延遲到子類去了,而且還通過父類的定義來約束了子類的行為,從而使系統能有更好的複用性和擴充套件性。

好萊塢法則

什麼是好萊塢法則呢?簡單點說,就是“不要找我們,我們會聯絡你”。

模板方法模式很好的體現了這一點,做為父類的模板會在需要的時候,呼叫子類相應的方法,也就是由父類來找子類,而不是讓子類來找父類。

這其實也是一種反向的控制結構,按照通常的思路,是子類找父類才對,也就是應該是子類來呼叫父類的方法,因為父類根本就不知道子類,而子類是知道父類的,但是在模板方法模式裡面,是父類來找子類,所以是一種反向的控制結構。

對設計原則的體現

模板方法很好的體現了開閉原則和里氏替換原則。

首先從設計上,先分離變與不變,然後把不變的部分抽取出來,定義到父類裡面,比如演算法骨架,比如一些公共的、固定的實現等等。這些不變的部分被封閉起來,儘量不去修改它了,要擴充套件新的功能,那就使用子類來擴充套件,通過子類來實現可變化的步驟,對於這種新增功能的做法是開放的。

其次,能夠實現統一的演算法骨架,通過切換不同的具體實現來切換不同的功能,一個根本原因就是里氏替換原則,遵循這個原則,保證所有的子類實現的是同一個演算法模板(為了防止子類改變模板方法中的演算法,可以將模板方法宣告為final),並能在使用模板的地方,根據需要,切換不同的具體實現。

模板方法模式的實現

模板方法呼叫下列型別的操作
  • 模板方法:就是定義演算法骨架的方法 。
  • 具體的操作:在模板中直接實現某些步驟的方法,通常這些步驟的實現演算法是固定的,而且是不怎麼變化的,因此就可以當作公共功能實現在模板裡面。如果不需提供給子類訪問這些方法的話,還可以是private的。這樣一來,子類的實現就相對簡單些。如果是子類需要訪問,可以把這些方法定義為protected final的,因為通常情況下,這些實現不能夠被子類覆蓋和改變了。
  • 具體的AbstractClass操作:在模板中實現某些公共功能,可以提供給子類使用,一般不是具體的演算法步驟的實現,只是一些輔助的公共功能。
  • 原語操作:就是在模板中定義的抽象操作,通常是模板方法需要呼叫的操作,是必需的操作,而且在父類中還沒有辦法確定下來如何實現,需要子類來真正實現的方法。
  • 鉤子操作:在模板中定義,並提供預設實現的操作。這些方法通常被視為可擴充套件的點,但不是必須的,子類可以有選擇的覆蓋這些方法,以提供新的實現來擴充套件功能。比如:模板方法中定義了5步操作,但是根據需要,某一種具體的實現只需要其中的1、2、3這幾個步驟,因此它就只需要覆蓋實現1、2、3這幾個步驟對應的方法。那麼4和5步驟對應的方法怎麼辦呢,由於有預設實現,那就不用管了。也就是說鉤子操作是可以被擴充套件的點,但不是必須的。
  • Factory Method:在模板方法中,如果需要得到某些物件例項的話,可以考慮通過工廠方法模式來獲取,把具體的構建物件的實現延遲到子類中去。

模板方法模式實現中指的注意的問題:

① 使用訪問控制:必須重定義的原語操作須定義為abstract函式。模板方法自身不需要被重定義,並且也不應該被重定義,為了防止子類改變模板方法中的演算法,可以將模板方法宣告為final。
② 儘量減少原語操作:定義模板方法的一個重要目的是儘量減少一個子類具體實現演算法時必須重定義的那些原語操作的數目。需要重定義的操作越多,客戶程式就越冗長。
③ 命名約定:可以給應被重定義的那些操作的名字加上一個字首以識別它們。

優缺點

優點:
實現程式碼複用
模板方法模式是一種實現程式碼複用的很好的手段。通過把子類的公共功能提煉和抽取,把公共部分放到模板裡面去實現。

缺點:
演算法骨架不容易升級
模板方法模式最基本的功能就是通過模板的制定,把演算法骨架完全固定下來。事實上模板和子類是非常耦合的,如果要對模板中的演算法骨架進行變更,可能就會要求所有相關的子類進行相應的變化。所以抽取演算法骨架的時候要特別小心,儘量確保是不會變化的部分才放到模板中。

相關模式

  • 模板方法模式 VS 工廠方法模式
    這兩個模式可以配合使用。
    模板方法模式可以通過工廠方法來獲取需要呼叫的物件。

  • 模板方法模式 VS 策略模式
    模板方法會定義一個演算法的大綱,然後由子類通過繼承來實現其中某些步驟的內容;策略模式則是通過物件組合的方式,讓客戶可以選擇演算法實現。即,使用委託來改變整個演算法。

    但是,我們可以在模板方法中使用策略模式,就是把那些變化的演算法步驟通過使用策略模式來實現,但是具體選取哪個策略還是要由外部來確定,而整體的演算法步驟,也就是演算法骨架就由模板方法來定義了。

參考

《Head First 設計模式》
《設計模式:可複用物件導向軟體的基礎》
《研磨設計模式》

相關文章