python設計模式之模版方法設計模式
我們在使用python的flask框架時,可能會經常用到生命周期函數如:before_request, before_first_request,或者訊號等,剛開始學的時候就想只要寫一個函數,然後加上一個裝飾器居然就可以實現這種開掛般的效果,那時感覺這框架程式碼寫得真棒, 再過些時間自己學會閱讀框架源碼時,在flask源碼中的wsgi_app函數裡面發現了奧秘,原來是這樣寫就能實現插入生命周期的效果啊,時間在走知識在漲,不知不覺走進了設計模式的天堂,再猛然看flask框架源碼的時候就覺得,原來如此,這不就是模版方法設計模式的具體應用嗎?接下來我們來看看什麼是模版方法設計模式來揭開它的神秘面紗。
模版方法設計模式GOF官方的解釋是: 定義一個操作中的演算法的骨架(穩定), 而將一些步驟(變化)延遲到子類中。 使得子類可以不改變(復用)一個演算法的結構即可重定義該演算法的某些特定步驟。
模版方法設計模式的框架圖如下:
可以看到在抽象類中定義了一系列固定流程的方法, 而在子類中去重寫或者實現具體的某些步驟。
接下來我們用丐版的Flask來演示模版方法設計模式的精髓,聲明flask框架並不是這樣實現的,只是含有模版設計模式的思想, 我們的演示只是把這思想展示出來。
01、沒有用設計模式Flask
class Flask: def before_request(self): pass def request(self): pass def context(self): print("我在存儲上下文") def response(self): pass def clear(self): print("我在清除上下文") class Application(Flask): def before_request(self): print("我在煮飯前加了一個蛋") def request(self): print("我正在吃飯") def response(self): print("終於吃好了") def run(self): self.before_request() self.request() self.context() self.request() self.clear() Application().run()
我們發現,run方法執行的步驟是固定的,這樣每個app繼承Flask的時候都要實現一個run方法,加重了app開發者的負擔,因為run主程式的步驟是固定的,我們把run方法的實現移到抽象類Flask中,看一下效果。
02、 用了模版設計模式的Flask
class Flask: def before_request(self): pass def request(self): pass def context(self): print("我在存儲上下文") def response(self): pass def clear(self): print("我在清除上下文") def run(self): self.before_request() self.request() self.context() self.request() self.clear() class Application(Flask): def before_request(self): print("我在煮飯前加了一個蛋") def request(self): print("我正在吃飯") def response(self): print("終於吃好了") Application().run()
這裡我們就是把主程式run方法移動到抽象類Flask中,這時作為我們開發者,我們只要實現具體的步驟如,before_request和request等就可以了,這樣大大減輕了開發者負擔。
03、什麼時候使用模版方法設計模式
在構建過程中,對於某一項任務,它通常有穩定的整體操作結構, 但各個子步驟卻有很多改變的需求,或者由於固有的原因(比如框架與應用之間的關係)而無法和任務的整體結構同時實現。在這個時候模版方法設計模式將會是你很好的一個選擇。
04、總結
模版方法設計模式是一種非常基礎性的設計模式, 在面向對象系統中有大量的應用。它用最簡潔的機制(多態)為很多應用程式框架提供了靈活的擴展點,是程式碼復用方面的基本實現結構。
除了可以靈活應對子步驟的變化外, 「不要調用我, 讓我來調用你」的反向控制結構是模版方法設計模式的典型應用。
最後還是奉上設計模式的8大基本設計原則:
- 依賴倒置原則(DIP)
- 高層模組(穩定)不應該依賴於低層模組(變化),二者都應該依賴於抽象(穩定) 。
- 抽象(穩定)不應該依賴於實現細節(變化) ,實現細節應該依賴於抽象(穩定)。
- 開放封閉原則(OCP)
- 對擴展開放,對更改封閉。
- 類模組應該是可擴展的,但是不可修改。
- 單一職責原則(SRP)
- 一個類應該僅有一個引起它變化的原因。
- 變化的方向隱含著類的責任。
- Liskov 替換原則(LSP)
- 子類必須能夠替換它們的基類(IS-A)。
- 繼承表達類型抽象。
- 介面隔離原則(ISP)
- 不應該強迫客戶程式依賴它們不用的方法。
- 介面應該小而完備。
- 優先使用對象組合,而不是類繼承
- 類繼承通常為「白箱復用」,對象組合通常為「黑箱復用」 。
- 繼承在某種程度上破壞了封裝性,子類父類耦合度高。
- 而對象組合則只要求被組合的對象具有良好定義的介面,耦合度低。
- 封裝變化點
- 使用封裝來創建對象之間的分界層,讓設計者可以在分界層的一側進行修改,而不會對另一側產生不良的影響,從而實現層次間的松耦合。
- 針對介面編程,而不是針對實現編程
- 不將變數類型聲明為某個特定的具體類,而是聲明為某個介面。
- 客戶程式無需獲知對象的具體類型,只需要知道對象所具有的介面。
- 減少系統中各部分的依賴關係,從而實現「高內聚、松耦合」的類型設計方案