設計模式 c++版(4)——模板方法模式
定義:
定義一個操作中的演算法的框架,而將一些步驟延遲到子類中。使得子類可以不改變一個演算法的結構即可重定義該演算法的某些特定步驟。
示例一:模板方法模式(通用版)
1. 類圖10-3
2. 類圖說明:
AbstractClass 叫抽象模板,他的方法分為兩類:
基本方法:即基本操作,是由子類實現的方法,並且在模板方法被呼叫。
模板方法:可以有一個或幾個,一般是一個具體方法,即一個框架,實現對基本方法的排程,完成固定的邏輯
注:為了防止被惡意操作,C++中可以使用兩種方式
- 模板方法最後加上final關鍵字,不允許被覆寫
- 模板方法為非虛擬函式,不允許被過載
類圖中的 ConcreteClass1 和 ConcreteClass2 屬於具體模板,實現父類所定義的一個或多個抽象方法,也就是父類定義的基本方法在子類中得以實現。
3. 程式碼清單10-2:
注:抽象模板中的基本方法儘量設計為 protected 型別,符合迪米特法則,不需要暴露的屬性或方法儘量不要設定為 protected 型別。實現類若非必要,儘量不要擴大父類中的訪問許可權。
////////// ********** 2.抽象模板 ,程式碼清單10-2:***************// #include <QCoreApplication> #include <QDebug> class AbstractClass { protected: virtual void doSomething() = 0; //基本方法 virtual void doAngthing() = 0; public: void templateMethod() //模板方法 { this->doSomething(); this->doAngthing(); } }; class ConcreteClass1:public AbstractClass { protected: virtual void doSomething() {qDebug() << "ConcreteClass1 : doSomething";} virtual void doAngthing() {qDebug() << "ConcreteClass1 : doAngthing";} }; class ConcreteClass2:public AbstractClass { protected: virtual void doSomething() {qDebug() << "ConcreteClass2 : doSomething";} virtual void doAngthing() {qDebug() << "ConcreteClass2 : doAngthing";} }; int main () { AbstractClass *class1 = new ConcreteClass1(); AbstractClass *class2 = new ConcreteClass2(); class1->templateMethod(); class2->templateMethod(); delete class1; delete class2; return 0; }
示例二:汽車模型
1. 類圖10-1
2. 類圖說明:
悍馬車有兩個型號,H1和H2。按需求,只需要給出悍馬車模型。此處有一個抽象類,然後兩個不同型號的模型實現類,通過簡單繼承就可以實現業務需求。
3. 結構說明:
HummerModel 抽象類,在類中定義了發動、停止、鳴笛、引擎、跑幾個方法。根據不同型號來進行不同的實現。
4. 程式碼清單10-1:
//////// ********** 1.抽象悍馬模型 ,程式碼清單10-1:***************// #include <QCoreApplication> #include <QDebug> class HummerModel { protected: virtual void start() = 0; virtual void stop() = 0; virtual void alarm() = 0; virtual void enginBoom() = 0; public: virtual void run() { this->start(); this->enginBoom(); this->alarm(); this->stop(); } }; class HummerH1Model:public HummerModel { protected: virtual void start() {qDebug() << "HummerH1 : start";} virtual void stop() {qDebug() << "HummerH1 : stop";} virtual void alarm() {qDebug() << "HummerH1 : alarm";} virtual void enginBoom() {qDebug() << "HummerH1 : enginBoom";} }; class HummerH2Model:public HummerModel { protected: virtual void start() {qDebug() << "HummerH2 : start";} virtual void stop() {qDebug() << "HummerH2 : stop";} virtual void alarm() {qDebug() << "HummerH2 : alarm";} virtual void enginBoom() {qDebug() << "HummerH2 : enginBoom";} }; int main() { HummerModel *h1 = new HummerH1Model(); HummerModel *h2 = new HummerH2Model(); h1->run(); h2->run(); return 0; }
三、模板方法模式的應用
優點:
- 封裝不變部分,擴充套件可變部分。例如,汽車模型中,增加一個H3型號的模型,增加一個子類,實現父類的基本方法就可以。
- 提取公共部分程式碼,便於維護。
- 行為由父類控制,子類實現。
缺點:
一般設計習慣,抽象類負責宣告最抽象的事物屬性和方法,實現類完成具體的事物屬性和方法。但是模板方法點到了,抽象類定義了部分抽象方法,由子類實現,子類執行的結果影響了父類的結果,也就是子類對父類產生了影響,在複雜的專案中,會帶來程式碼閱讀的難度。
使用場景:
- 多個子類有共有的方法,並且邏輯基本相同時。
- 重要、複雜的演算法,可以把核心演算法設計為模板方法,周邊相關細節功能由各個子類實現。
- 重構時,把相同程式碼抽取到父類中,通過鉤子函式(見“模板方法模式的擴充套件”)約束其行為。
四、模板方法模式的擴充套件
示例三: 汽車模型
增加需求 H1的喇叭是否響可控,H2的喇叭不響
1. 類圖10-4:
2. 類圖說明:
在抽象類 HummerModel 中增加了一個實現方法 isAlarm,確定各個型號的車是否需要聲音,由各個實現類覆寫該方法,同時其他基本方法由於不需要對外提供訪問,因此也設計為 protected 型別
3. 程式碼清單10-1:
////////// ********** 3.抽象悍馬模型擴充套件 ,程式碼清單10-3:***************//
class HummerModel
{
protected:
virtual void start() = 0;
virtual void stop() = 0;
virtual void alarm() = 0;
virtual void enginBoom() = 0;
virtual bool isAlarm() {return true;}
public:
void run()
{
this->start();
this->enginBoom();
if (this->isAlarm())
{
this->alarm();
}
this->stop();
}
};
class HummerH1Model:public HummerModel
{
public:
void setAlarm(bool OK){this->m_alarmFlag = OK;}
protected:
virtual void start() {qDebug() << "HummerH1 : start";}
virtual void stop() {qDebug() << "HummerH1 : stop";}
virtual void alarm() {qDebug() << "HummerH1 : alarm";}
virtual void enginBoom() {qDebug() << "HummerH1 : enginBoom";}
virtual bool isAlarm() {return this->m_alarmFlag;}
private:
bool m_alarmFlag;
};
class HummerH2Model:public HummerModel
{
protected:
virtual void start() {qDebug() << "HummerH2 : start";}
virtual void stop() {qDebug() << "HummerH2 : stop";}
virtual void alarm() {qDebug() << "HummerH2 : alarm";}
virtual void enginBoom() {qDebug() << "HummerH2 : enginBoom";}
virtual bool isAlarm() {return false;}
};
int main()
{
HummerH1Model h1;
HummerH2Model h2;
h1.setAlarm(true);
h1.run();
h2.run();
return 0;
}
五、最佳實踐
提問:父類是否可以呼叫子類方法?
回答:能,但是不建議這麼做,合適的3個方式如下:
- 把子類傳遞到父類的有參構造中,然後呼叫。
- 使用反射的方式呼叫。
- 父類呼叫子類的靜態方法。
以上方法可以解決問題,但不建議用父類呼叫子類方法。
合適的方法:
父類建立框架,子類重寫了父類部分方法後,再呼叫從父類繼承的方法,產生不同的結果,即模板方式。
開元框架中的使用:
開元框架提供了一個抽象類,然後有一堆子類,如果需要擴充套件功能,可以繼承這個抽象類,然後覆寫 protected 方法,然後呼叫一個類似 execute方法,即可擴充套件。
參考文獻《秦小波. 設計模式之禪》(第2版) (華章原創精品) 機械工業出版社