每次看到專案中存在大量的if else程式碼時,都會心生一絲不安全感。 特別是產品給的需求需要新增或者更改一種if條件時,生怕會因為自己的疏忽而使程式碼天崩地裂,哈哈,本文的目的就是來解決這種不安全感的,23種設計模式的策略模式。
GOF對策略模式的解釋是: 定義一系列演算法, 把它們一個個封裝起來,並且使它們可相互替換(變化)。該模式使得演算法可獨立於使用它的客戶程式(穩定)而變化(擴充套件, 子類化)。
策略模式結構圖如下:
生活中商場搞活動給消費者派送了一批優惠券,優惠券分三種, 一種是滿減券滿100減10元, 一種是打折券滿200打8折, 一種折扣券是滿300打7.5折然後送雞蛋一打(價值15),如果我們是商場後臺程式設計師,我們要計算每個客戶最後實際支付了多少錢,那這個程式你會怎樣去設計?
01、 沒有使用設計模式的版本
class Order: def __init__(self, money, coupon): self.money = money self.coupon = coupon def total_money(self): if self.coupon == 1 and self.money >= 100: return self.money - 10 elif self.coupon == 2 and self.money >= 200: return self.money * 0.8 elif self.coupon == 3 and self.money >= 300: return self.money * 0.75 -15 return self.money if __name__ == '__main__': order1 = Order(102, 1) order2 = Order(250, 2) order3 = Order(372, 3) order4 = Order(190, 2) o1 = order1.total_money() o2 = order2.total_money() o3 = order3.total_money() o4 = order4.total_money() print(o1, o2, o3, o4)
在平常的開發過程中,我們及其容易設計出這樣的程式碼,夾雜著大量的if else語句,程式耦合性很高,一旦發生修改不小心就天崩地裂了, 嚴重違反我們八大設計原則的開閉原則。
接下來我們用策略設計模式來設計我們的程式。
02、首先設計策略基類
from abc import ABC, abstractmethod class BaseStrategy(ABC): @abstractmethod def calculate(cls, money): pass
策略基類繼承ABC類(抽象類)在方法上加上abstractmethod表示子類必須重寫這個方法,不然會報錯。
03、設計策略子類
class FullOneHundredStrategy(BaseStrategy): @classmethod def calculate(cls, money): if money >= 100: return money - 10 return money class FullTwoHundredStrategy(BaseStrategy): @classmethod def calculate(cls, money): if money >= 200: return money * 0.8 return money class FullThreeHundredStrategy(BaseStrategy): @classmethod def calculate(cls, money): if money >= 300: return money * 0.75 - 15 return money
分別設計三個策略子類並重寫對應的計算方法,如果以後新增新的優惠券就可以增加一個新的子類即可。
04、設計訂單類
class Order: coupon_map = { 1: FullOneHundredStrategy, 2: FullTwoHundredStrategy, 3: FullThreeHundredStrategy } def __init__(self, money, coupon): self.money = money self.coupon = coupon def total_money(self): return self.coupon_map.get(self.coupon).calculate(self.money)
訂單類包括例項屬性money,和優惠券的種類, 並有一個類屬性map,在實際開發中這個Map一般會放到公共檔案中。
05、上下文中計算總金額
if __name__ == '__main__': order1 = Order(102, 1) order2 = Order(250, 2) order3 = Order(372, 3) order4 = Order(190, 2) o1 = order1.total_money() o2 = order2.total_money() o3 = order3.total_money() o4 = order4.total_money() print(o1, o2, o3, o4)
我們列印結果:
和我們期望的結果一樣,這樣就完美的消除了大量的if else語句了, 更重要的是這樣使策略與訂單類解耦了,增加了程式的可複用性,和維護性。
06、總結
策略設計模式的好處:
- Strategy及其子類為元件提供了一系列可重用的演算法, 從而可以使得型別在執行時方便地根據需要在各個演算法之間進行切換。
- Strategy模式提供了用條件判斷語句以外的另一種選擇,消除條件判斷語句,就是在解耦合。含有許多條件判斷語句的程式碼通常需要Strategy模式。
策略模式的劣處:
- 當策略越來愈多,就需要寫更多的策略子類,後期接手的人可能就會有點懵了。
- 消除的if else判斷語句僅適用於前後判斷之間沒有強關聯性的情況, 如果判斷有強關聯可能需要用到另一個設計模式責任鏈設計模式。
ps:前後判斷強關聯如上述舉例改成:商場活動不需要優惠券,只要你消費滿100就減10,滿200打8折,這樣前後判斷就有比較強的關聯性了,此時的策略模式就不能有效消除if else判斷語句了。
在最後我們依然需要加上我們設計模式的八大原則:
- 依賴倒置原則(DIP)
- 高層模組(穩定)不應該依賴於低層模組(變化),二者都應該依賴於抽象(穩定) 。
- 抽象(穩定)不應該依賴於實現細節(變化) ,實現細節應該依賴於抽象(穩定)。
- 開放封閉原則(OCP)
- 對擴充套件開放,對更改封閉。
- 類模組應該是可擴充套件的,但是不可修改。
- 單一職責原則(SRP)
- 一個類應該僅有一個引起它變化的原因。
- 變化的方向隱含著類的責任。
- Liskov 替換原則(LSP)
- 子類必須能夠替換它們的基類(IS-A)。
- 繼承表達型別抽象。
- 介面隔離原則(ISP)
- 不應該強迫客戶程式依賴它們不用的方法。
- 介面應該小而完備。
- 優先使用物件組合,而不是類繼承
- 類繼承通常為“白箱複用”,物件組合通常為“黑箱複用” 。
- 繼承在某種程度上破壞了封裝性,子類父類耦合度高。
- 而物件組合則只要求被組合的物件具有良好定義的介面,耦合度低。
- 封裝變化點
- 使用封裝來建立物件之間的分界層,讓設計者可以在分界層的一側進行修改,而不會對另一側產生不良的影響,從而實現層次間的鬆耦合。
- 針對介面程式設計,而不是針對實現程式設計
- 不將變數型別宣告為某個特定的具體類,而是宣告為某個介面。
- 客戶程式無需獲知物件的具體型別,只需要知道物件所具有的介面。
- 減少系統中各部分的依賴關係,從而實現“高內聚、鬆耦合”的型別設計方案