Abstract
template method pattern是我學到第二個pattern,算是一個很容易理解的pattern,但卻非常的實用。
Intent
對於operation,只先定義好演算法的輪廓,某些步驟則留給子類別去填補,以便在不改變演算法整體架構的情況下讓子類別去精鏈某些步驟。
其UML表示法
在實務上,我們可能本來有一個功能完整的class,但因為『需求改變』,新的class和原來的class幾乎60%相同, 只有40%不一樣,因此我們希望將60%相同部份的程式留下來,僅改寫40%不同的地方。
如本來某公司只生產『自動泡茶機』,後來為了增加產品線,想生產『自動泡咖啡機』,經過分析,兩台機器的架構相似,生產流程也相似。
自動泡茶機
step1 : 將開水煮開
step2 : 將<茶葉>放入開水
step3 : 將<茶>倒入杯子
step4 : 加上<檸檬>
自動泡咖啡機
step1 : 將開水煮開
step2 : 將<咖啡粉>放入開水
step3 : 將<咖啡>倒入杯子
step4 : 加上<糖>和<奶精>
很明顯的step1相同,但step2 ~ step4不相同,所以只需改寫step2 ~ step4,step1可以繼續使用。先設計一個DrinkMachine雛型,定義了生產過程和step1,因為step2 ~ step4各有差異,就留在繼承DrinkMachine的class去改寫,這就是template method pattern。
我們看看這個架構,日後若有新drink加入,DrinkMachine,TeaMachine,CoffeeMachine皆不用修改,符合OCP的closed for modification原則,若要加入新的class,只計程並改寫DrinkMachine即可,符合OCP的open for extension原則,所以是非常好維護的架構。
簡言之,template method pattern就是將不同的member function用class包起來,由derived class去改寫。
以下用C++實做template method pattern。
1/**//*
2(C) OOMusou 2007 http://oomusou.cnblogs.com
3
4Filename : DP_TemplateMethodPattern1.cpp
5Compiler : Visual C++ 8.0 / BCB 6.0 / gcc 3.4.2 / ISO C++
6Description : Demo how to use Template Method Pattern
7Release : 03/31/2007 1.0
8*/
9
10#include <iostream>
11
12using namespace std;
13
14class DrinkMachine {
15public:
16 void makeDrink();
17
18protected:
19 void boilWater() { cout << "boil some water" << endl; }
20 virtual void doPutIngredient() const = 0;
21 virtual void doPourInCup() const = 0;
22 virtual void doAddFlavoring() const = 0;
23};
24
25void DrinkMachine::makeDrink() {
26 this->boilWater();
27 this->doPutIngredient();
28 this->doPourInCup();
29 this->doAddFlavoring();
30}
31
32class TeaMachine : public DrinkMachine {
33protected:
34 void doPutIngredient() const { cout << "steep tea in boiling water" << endl; }
35 void doPourInCup() const { cout << "pour tea in cup" << endl; }
36 void doAddFlavoring() const {cout << "add lemon" << endl; }
37};
38
39class CoffeeMachine : public DrinkMachine {
40protected:
41 void doPutIngredient() const { cout << "brew coffee in boiling water" << endl; }
42 void doPourInCup() const { cout << "pour coffee in cup" << endl; }
43 void doAddFlavoring() const {cout << "add sugar and milk." << endl; }
44};
45
46int main() {
47 cout << "Making Tea" << endl;
48
49 DrinkMachine *theDrinkMachine = &TeaMachine();
50 theDrinkMachine->makeDrink();
51
52 cout << endl;
53
54 cout << "Making Coffee" << endl;
55 theDrinkMachine = &CoffeeMachine();
56 theDrinkMachine->makeDrink();
57}
執行結果
Making Tea
boil some water
steep tea in boiling water
pour tea in cup
add lemon
Making Coffee
boil some water
brew coffee in boiling water
pour coffee in cup
add sugar and milk.
感謝Quark提供template版本的template method寫法
1#include <iostream>
2
3using namespace std;
4
5template<typename T>
6class DrinkMachine {
7public:
8 void makeDrink() {
9 T* derived =(T*) this;
10
11 this->boilWater();
12 derived->doPutIngredient();
13 derived->doPourInCup();
14 derived->doAddFlavoring();
15 }
16
17protected:
18 void boilWater() {
19 cout << "boil some water" << endl;
20 }
21};
22
23class TeaMachine : public DrinkMachine<TeaMachine> {
24friend DrinkMachine<TeaMachine>;
25protected:
26 void doPutIngredient() const { cout << "steep tea in boiling water" << endl; }
27 void doPourInCup() const { cout << "pour tea in cup" << endl; }
28 void doAddFlavoring() const {cout << "add lemon" << endl; }
29};
30
31class CoffeeMachine:public DrinkMachine<CoffeeMachine> {
32friend DrinkMachine<CoffeeMachine>;
33protected:
34 void doPutIngredient() const { cout << "brew coffee in boiling water" << endl; }
35 void doPourInCup() const { cout << "pour coffee in cup" << endl; }
36 void doAddFlavoring() const {cout << "add sugar and milk." << endl; }
37};
38
39int main() {
40 cout << "Making Tea" << endl;
41 DrinkMachine<TeaMachine> *pTeaMachine = &TeaMachine();
42 pTeaMachine->makeDrink();
43
44 cout << endl;
45
46 cout << "Making Coffee" << endl;
47 DrinkMachine<CoffeeMachine> *pCoffeeMachine = &CoffeeMachine();
48 pCoffeeMachine->makeDrink();
49}
50
Remark
strategy和template method目的相同,皆對『新需求』的不同演算法提供『擴充』的機制,但手法卻不同,strategy采用object的方式,利用delegation改變algorithm,而template method則采用class的繼承方式來改變algorithm,由於用到的是class的inheritance,所以在compile-time就已經決定要override的algorithm,run-time就無法再改了,但strategy用的是object手法,所以在run-time還可以透過換object改變algorithm。
GoF的原文如下
Template methods use inheritance to vary part of an algorithm. Strategies use delegation to vary the entire algorithm.