C++設計模式之模板模式(template)(行爲型)

一 定義

模板方法模式是一種類的行爲型模式,在它的結構圖中只有類之間的繼承關係,沒有對象關聯關係,模板方法模式(Template Method Pattern)官方定義:定義一個操作中的算法的框架,而將一些步驟延遲到子類中,使得子類可以不改變一個算法的結構即可重定義該算法的某些特定步驟。

抽象模板中包含三種類型的方法: 基本方法模板方法鉤子方法(Hook Method)。

基本方法——基本方法也叫做基本操作,是由子類實現的方法,並且在模板方法被調用。

模板方法——核心方法,不允許子類重寫,所以都會加上final修飾符,可以有一個或幾個,一般是一個具體方法框架,按照固定的流程對基本方法的調度

鉤子方法——爲了讓模板方法的執行結果的更好地適應因外界條件改變。比如說銀行辦理業務爲例,辦理業務是個模板方法,普通人要經歷排隊、取號、等待、辦理四個基本流程,而Vip則不需要排隊、取號、等待,那麼在設計的時候我們的抽象模板類需要考慮到,於是乎你得需要在模板方法各基本方法調用之前增加條件判斷,那麼用於設置這個條件的方法就是鉤子方法(Hook Method),鉤子方法也可以是抽象的還可以由子類的一個方法返回值決定公共部分的執行結果。

二 ULM圖

角色:

(1)AbstractClass:是抽象類,其實也就是一個抽象模板,定義並實現了一個模板方法。這個模板方法一般是一個具體方法,它給出了一個頂級邏輯的框架,而邏輯的組成步驟在相應的抽象操作中,推遲到子類實現。頂級邏輯也有可能調用一些具體方法。

(2)ConcreteClass:實現父類所定義的一個或多個抽象方法。每一個AbstractClass都可以有任意多個ConcreteClass與之對應,而每一個ConcreteClass都可以給出這些抽象方法(也就是頂級邏輯的組成步驟)的不同實現,從而使得頂級邏輯的實現各不相同。

總結:當不變的和可變的行爲在方法的子類實現中混合在一起的時候,不變的行爲就會在子類中重複出現。我們通過模板方法模式,把這些行爲搬移到單一的地方,這樣幫助子類擺脫重複的不變行爲的糾纏。

模板方法模式的優點

良好的擴展性,封裝不變部分,擴展可變部分,把認爲是不變部分的算法封裝到父類實現,而可變部分的則可以通過繼承來繼續擴展。例如增加一個新的功能很簡單,只要再增加一個子類,實現父類的基本方法就可以了。

提取公共部分代碼,便於維護,減小維護升級成本,基本操作由父類定義,子類實現

基本方法是由子類實現的,因此子類可以通過擴展的方式增加相應的功能,符合開閉原 則。

模板方法模式的缺點

通常抽象類是負責聲明某一類的事物的共同屬性和抽象方法,實現類則是完成定義具體的特性和方法。但是模板方法模式卻顛倒了,抽象類定義了部分抽象方法,由子類實現,子類執行的結果影響了父類的結果,也就是子類對父類產生了影響。每個不同的實現都需要定義一個子類,這會導致類的個數增加,系統更加龐大,設計也更加抽象,但是更加符合“單一職責原則”,使得類的內聚性得以提高。

適合使用模板方法模式的場景

  1. 多個子類有公有的方法,並且邏輯基本相同時。
  2. 重要、複雜的算法,可以把核心算法設計爲模板方法,周邊的相關細節功能則由各個 子類實現。
  3. 重構時,模板方法模式是一個經常使用的模式,把相同的代碼抽取到父類中,然後通過鉤子方法約束其行爲。

 四 實例

1. 填寫簡歷表

最近有個招聘會,可以帶上簡歷去應聘了。但是,其中有一家公司不接受簡歷,而是給應聘者發了一張簡歷表,上面有基本信息、教育背景、工作經歷等欄,讓應聘者按照要求填寫完整。每個人拿到這份表格後,就開始填寫。如果用程序實現這個過程,該如何做呢?一種方案就是用模板方法模式:定義一個操作中的算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類可以不改變一個算法的結構即可重定義該算法的某些特定步驟。我們的例子中,操作就是填寫簡歷這一過程,我們可以在父類中定義操作的算法骨架,而具體的實現由子類完成。下面給出它的UML圖。

 

#include <iostream>
#include <memory>

//簡歷
class Resume
{
protected: //保護成員
    virtual void SetPersonalInfo() {}
    virtual void SetEducation() {}
    virtual void SetWorkExp() {}
public:
    void FillResume()
    {
        SetPersonalInfo();
        SetEducation();
        SetWorkExp();
    }
};

class ResumeA: public Resume
{
protected:
    void SetPersonalInfo()
    {
        std::cout << "A's PersonalInfo" << std::endl;
    }
    void SetEducation()
    {
        std::cout << "A's Education" << std::endl;
    }
    void SetWorkExp()
    {
        std::cout << "A's Work Experience" << std::endl;
    }
};

class ResumeB: public Resume
{
protected:
    void SetPersonalInfo()
    {
        std::cout << "B's PersonalInfo" << std::endl;
    }
    void SetEducation()
    {
        std::cout << "B's Education" << std::endl;
    }
    void SetWorkExp()
    {
        std::cout << "B's Work Experience" << std::endl;
    }
};

int main()
{
    auto r1 = std::make_unique<ResumeA>();
    r1->FillResume();

    auto r2 = std::make_unique<ResumeB>();
    r2->FillResume();

    return 0;
}

 

五 模式的擴展

1)模板方法模式與控制反轉(好萊塢原則)

在模板方法模式中,子類不顯式調用父類的方法,而是通過覆蓋父類的方法來實現某些具體的業務邏輯,父類控制對子類的調用,這種機制被稱爲好萊塢原則(Hollywood Principle),好萊塢原則的定義爲:“不要給我們打電話,我們會給你打電話(Don‘t call us, we’ll call you)”。在好萊塢,把簡歷遞交給演藝公司後就只有回家等待。由演藝公司對整個娛樂項的完全控制,演員只能被動式的接受公司的差使,在需要的環節中,完成自己的演出。模板方法模式充分的體現了“好萊塢”原則。由父類完全控制着子類的邏輯,子類不需要調用父類,而通過父類來調用子類,子類可以實現父類的可變部份,卻繼承父類的邏輯,不能改變業務邏輯。

2)模板方法模式符合開閉原則
模板方法模式意圖是由抽象父類控制頂級邏輯,並把基本操作的實現推遲到子類去實現,這是通過繼承的手段來達到對象的複用,同時也遵守了開閉原則。

父類通過頂級邏輯,它通過定義並提供一個具體方法來實現,我們也稱之爲模板方法。通常這個模板方法纔是外部對象最關心的方法。在上面的銀行業務處理例子中,templateMethodProcess這個方法纔是外部對象最關心的方法。所以它必須是public的,才能被外部對象所調用。

子類需要繼承父類去擴展父類的基本方法,但是它也可以覆寫父類的方法。如果子類去覆寫了父類的模板方法,從而改變了父類控制的頂級邏輯,這違反了“開閉原則”。我們在使用模板方法模式時,應該總是保證子類有正確的邏輯。所以模板方法應該定義爲final的。所以AbstractClass類的模板方法templateMethodProcess方法應該定義爲final。

模板方法模式中,抽象類的模板方法應該聲明爲final的。因爲子類不能覆寫一個被定義爲final的方法。從而保證了子類的邏輯永遠由父類所控制。

3)模板方法模式與對象的封裝性

面向對象的三大特性:繼承,封裝,多態。

對象有內部狀態和外部的行爲。封裝是爲了信息隱藏,通過封裝來維護對象內部數據的完整性。使得外部對象不能夠直接訪問一個對象的內部狀態,而必須通過恰當的方法才能訪問。

對象屬性和方法賦予指定的修改符(public、protected、private)來達到封裝的目的,使得數據不被外部對象惡意的訪問及方法不被錯誤調用導造成破壞對象的封裝性。

降低方法的訪問級別,也就是最大化的降低方法的可見度是一種很重要的封裝手段。最大化降低方法的可見度除了可以達到信息隱藏外,還能有效的降低類之間的耦合度,降低一個類的複雜度。還可以減少開發人員發生的的錯誤調用。

一個類應該只公開外部需要調用的方法。而所有爲public方法服務的方法都應該聲明爲protected或private。如是一個方法不是需要對外公開的,但是它需要被子類進行擴展的或調用。那麼把它定義爲protected.否則應該爲private。

顯而易見,模板方法模式中的聲明爲abstract的基本操作都是需要迫使子類去實現的,它們僅僅是爲模板方法服務的。它們不應該被抽象類(AbstractClass)所公開,所以它們應該protected。

因此模板方法模式中,迫使子類實現的抽象方法應該聲明爲protected abstract。

4)模板方法與勾子方法(hookMethod)

模板方法模式的抽象類定義方法:
模板方法:一個模板方法是定義在抽象類中的、把基本操作方法組合在一起形成一個總算法或一個總行爲的方法。
基本方法:基本方法是實現算法各個步驟的方法,是模板方法的組成部分。基本方法如下:
•抽象方法(Abstract Method)
•具體方法(Concrete Method)
•鉤子方法(Hook Method):“掛鉤”方法和空方法,
hook方法在抽象類中的實現爲空,是留給子類做一些可選的操作。如果某個子類需要一些特殊額外的操作,則可以實現hook方法,當然也可以完全不用理會,因爲hook在抽象類中只是空方法而已。
1)鉤子方法的引入使得子類可以控制父類的行爲。
2)最簡單的鉤子方法就是空方法,也可以在鉤子方法中定義一個默認的實現,如果子類不覆蓋鉤子方法,則執行父類的默認實現代碼。
3)比較複雜一點的鉤子方法可以對其他方法進行約束,這種鉤子方法通常返回一個boolean類型,即返回true或false,用來判斷是否執行某一個基本方法。由子類來決定是否調用hook方法。

5) 小結

1)模板方法模式是一種類的行爲型模式,在它的結構圖中只有類之間的繼承關係,沒有對象關聯關係。
2)板方法模式是基於繼承的代碼複用基本技術,模板方法模式的結構和用法也是面向對象設計的核心之一。在模板方法模式中,可以將相同的代碼放在父類中,而將不同的方法實現放在不同的子類中。
3)在模板方法模式中,我們需要準備一個抽象類,將部分邏輯以具體方法以及具體構造函數的形式實現,然後聲明一些抽象方法來讓子類實現剩餘的邏輯。不同的子類可以以不同的方式實現這些抽象方法,從而對剩餘的邏輯有不同的實現,這就是模板方法模式的用意。模板方法模式體現了面向對象的諸多重要思想,是一種使用頻率較高的模式。

 

發佈了243 篇原創文章 · 獲贊 37 · 訪問量 25萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章