python設計模式-模板方法模式

goodspeed發表於2019-02-16

date: 2018-12-02T17:23:56+08:00
description: python 設計模式 模板方法模式
draft: false
slug: “python-design-pattern-template-pattern”
categories: [“development”, “python”, “設計模式”]
tags: [“python”, “讀書筆記”, “設計模式”]

title: “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_recipeboil_waterpour_in_cup 這三個方法。

問題:如何重新設計這兩個類來讓程式碼更簡潔呢?

首先看一下兩個類的類圖:

2bd637fbc0532bd0bafbf861005fabd3.png

  1. 每個類中都有 prepare_recipe() boil_water() pour_in_cup()方法。
  2. 每個類中prepare_recipe()方法的實現都不一樣。

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

ce3e56409c313d4ce42e9878f3b5c8f4.png

因為每個類中prepare_recipe()實現的方法不一樣,所以TeaCoffee 類都分別實現了 prepare_recipe()
問題: 那麼,有沒有辦法將prepare_recipe()也抽象化?

對比 TeaCoffeeprepare_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 不需要此方法,可以用空的實現代替

新的類圖如下:

bff9cea3d8f8226b6a9bb29c1deb8d88.png

現在,類 TeaCoffee 只需要實現具體的 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 就是一個模板方法。

定義:模板方法牧師在一個方法中定義一個演算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類可以在不改變演算法結構的情況下,重新定義演算法中的某些步驟。

優點

  1. 使用模板方法可以將程式碼的複用最大化
  2. 子類只需要實現自己的方法,將演算法和實現的耦合降低。

好萊塢原則

模板方法使用到了一個原則,好萊塢原則

好萊塢原則,別呼叫我,我會呼叫你。

好萊塢原則
在這個原則之下,允許低層元件將自己掛鉤到系統上,但是由高層元件來決定什麼時候使用這些低層元件。

在上邊的例子中,CoffeineBeverage 是高層元件,Coffee和Tea 是低層元件,他們不會之間呼叫抽象類(CoffeineBeverage)。

一個例子?

Python 第三方表單驗證包 wtforms 的表單驗證部分就使用到了模板方法模式。Field 類中validate方法就是一個模板方法,在這個方法中,會呼叫 pre_validate_run_validation_chainpost_validate方法來驗證表單,這些方法也都可以在子類中重新實現。具體實現可以參考以下原始碼。

原始碼地址:https://github.com/wtforms/wtforms/blob/master/src/wtforms/fields/core.py

參考連結


本文例子來自《Head First 設計模式》

最後,感謝女朋友支援和包容,比❤️

也可以在公號輸入以下關鍵字獲取歷史文章:公號&小程式 | 設計模式 | 併發&協程

相關文章