python設計模式-模板方法模式
首先先介紹一下咖啡和茶的沖泡方法:
茶
1. 把水煮沸 2. 用沸水浸泡茶葉 3. 把茶放到杯子裡 複製程式碼
咖啡
1. 把水煮沸 2. 用沸水沖泡咖啡 3. 把咖啡倒進杯子 4. 加糖和牛奶 複製程式碼
用python程式碼實現沖泡方法大概是這個樣子:
# 茶的製作方法 class Tea: def prepare_recipe(self): # 在下邊實現具體步驟 self.boil_water() self.brew_tea_bag() self.pour_in_cup() def boil_water(self): print("Boiling water") def brew_tea_bag(self): print("Steeping the tea") def pour_in_cup(self): print("Pouring into cup") 複製程式碼
# 咖啡的製作方法 class Coffee: def prepare_recipe(self): # 在下邊實現具體步驟 self.boil_water() self.brew_coffee_grinds() self.pour_in_cup() self.add_sugar_and_milk() def boil_water(self): print("Boiling water") def brew_coffee_grinds(self): print("Dripping Coffee through filter") def pour_in_cup(self): print("Pouring into cup") def add_sugar_and_milk(self): print("Adding Sugar and Milk") 複製程式碼
仔細看上邊兩端程式碼會發現,茶和咖啡的實現方式基本類似,都有 prepare_recipe
, boil_water
, pour_in_cup
這三個方法。
問題:
如何重新設計這兩個類來讓程式碼更簡潔呢?
首先看一下兩個類的類圖:

prepare_recipe() boil_water() pour_in_cup() prepare_recipe()
現在把 prepare_recipe() boil_water() pour_in_cup()
三個方法抽取出來做成一個父類 CoffeineBeverage()
, Tea
和 Coffee
都繼自 CoffeineBeverage()
。

因為每個類中 prepare_recipe()
實現的方法不一樣,所以 Tea
和 Coffee
類都分別實現了 prepare_recipe()
。 問題
: 那麼,有沒有辦法將 prepare_recipe()
也抽象化?
對比 Tea
和 Coffee
的 prepare_recipe()
方法會發現,他們之間的差異主要是:
def prepare_recipe(self): # 相同部分隱藏 # self.boil_water() self.brew_tea_bag()# 差異1 #self.pour_in_cup() def prepare_recipe(self): # 相同部分隱藏 # self.boil_water() self.brew_coffee_grinds() # 差異1 # self.pour_in_cup() self.add_sugar_and_milk() # 差異2 複製程式碼
這裡的實現思路是,將兩處差異分別用新的方法名代替,替換後結果如下:
def prepare_recipe(self): # 新的實現方法 self.boil_water() self.brew() # 差異1 使用brew 代替 brew_tea_bag 和 brew_coffee_grinds self.pour_in_cup() self.add_condiments() # 差異2 Tea 不需要此方法,可以用空的實現代替 複製程式碼
新的類圖如下:

現在,類 Tea
和 Coffee
只需要實現具體的 brew()
和 add_condiments()
方法即可。程式碼實現如下:
class CoffeineBeverage: def prepare_recipe(self): # 新的實現方法 self.boil_water() self.brew() self.pour_in_cup() self.add_condiments() def boil_water(self): print("Boiling water") def brew(self): # 需要在子類實現 raise NotImplementedError def pour_in_cup(self): print("Pouring into cup") def add_condiments(self): # 這裡其實是個鉤子方法,子類可以視情況選擇是否覆蓋 # 鉤子方法是一個可選方法,也可以讓鉤子方法作為某些條件觸發後的動作 pass # 茶的製作方法 class Tea(CoffeineBeverage): def brew(self): # 父類中聲明瞭 raise NotImplementedError,這裡必須要實現此方法 print("Steeping the tea") # Tea 不需要 add_condiments 方法,所以這裡不需要實現 # 咖啡的製作方法 class Coffee(CoffeineBeverage): def brew(self): # 父類中聲明瞭 raise NotImplementedError,這裡必須要實現此方法 print("Dripping Coffee through filter") def add_condiments(self): print("Adding Sugar and Milk") 複製程式碼
模板方法
上述抽象過程使用的就是模板方法。模板方法定義了一個演算法的步驟,並且允許子類為一個或多個步驟提供實現。在這個例子中, prepare_recipe
就是一個模板方法。
定義:
模板方法牧師在一個方法中定義一個演算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類可以在不改變演算法結構的情況下,重新定義演算法中的某些步驟。
優點
- 使用模板方法可以將程式碼的複用最大化
- 子類只需要實現自己的方法,將演算法和實現的耦合降低。
好萊塢原則
模板方法使用到了一個原則, 好萊塢原則
。
好萊塢原則
,別調用我,我會呼叫你。

在這個原則之下,允許低層元件將自己掛鉤到系統上,但是由高層元件來決定什麼時候使用這些低層元件。
在上邊的例子中,CoffeineBeverage 是高層元件,Coffee和Tea 是低層元件,他們不會之間呼叫抽象類(CoffeineBeverage)。
一個例子:chestnut:
Python 第三方表單驗證包 wtforms 的表單驗證部分就使用到了模板方法模式。Field 類中 validate
方法就是一個模板方法,在這個方法中,會呼叫 pre_validate
, _run_validation_chain
, post_validate
方法來驗證表單,這些方法也都可以在子類中重新實現。具體實現可以參考以下原始碼。
原始碼地址: ofollow,noindex">github.com/wtforms/wtf…
參考連結
本文例子來自《Head First 設計模式》
最後,感謝女朋友支援和包容,比:heart:
也可以在公號輸入以下關鍵字獲取歷史文章: 公號&小程式
| 設計模式
| 併發&協程
