【經典案例】Python詳解設計模式:策略模式

完成一項任務每每有多種方式,咱們將其稱之爲策略。html

好比,超市作活動,若是你的購物積分滿1000,就能夠按兌換現金抵用券10元,若是購買同一商品滿10件,就能夠打9折,若是若是購買的金額超過500,就能夠享受滿減50元的優惠。這是三個不一樣的促銷策略。python

再好比,聯繫朋友、同窗,能夠打電話,也能夠發短信,能夠發微信,也能夠發郵件,這是四個不一樣的聯繫策略。設計模式

再好比,去外出旅遊,咱們能夠選擇火車,也能夠選擇公共汽車,能夠選擇飛機,也能夠選擇自駕遊。這又是四個不一樣的出行策略。微信

以上這些真實場景,都有策略選擇模型的影子,能夠考慮使用策略模式。app

經典的策略模式,是由三部分組成ide

  • Context:上下文環境類
  • Stragety:策略基類
  • ConcreteStragety:具體策略

以第一個超市作活動的場景來舉個例子。函數

  • Context:Order類,訂單信息,包括商品,價格和數量,覺得購買者等
  • Stragety:Promotion類,抽象基類,包含一個抽象方法(計算折扣)
  • ContreteStragety:分三個類,FidelityPromo,BulkItemPromo,LargeOrderPromo,實現具體的折扣計算方法。

首先是 Order 類:學習

class Item:
    def __init__(self, issue, price, quantity):
        self.issue = issue
        self.price = price
        self.quantity = quantity

    def total(self):
        return self.price * self.quantity

class Order:
    def __init__(self, customer, promotion=None):
        self.cart = []
        self.customer = customer
        self.promotion = promotion

    def add_to_cart(self, *items):
        for item in items:
            self.cart.append(item)

    def total(self):
        total = 0
        for item in self.cart:
            total += item.total()

        return total

    def due(self):
        if not self.promotion:
            discount = 0
        else:
            discount  = self.promotion.discount(self)
        return (self.total() - discount)

而後是積分兌換現金券的策略,爲了保證咱們的代碼具備良好的可擴展性及維護性,我會先寫一個策略類,它是一個抽象基類,它的子類都是一個具體的策略,都必須實現 discount 方法,就好比我們的積分兌換現金策略。優化

from abc import ABC, abstractmethod

class Promotion(ABC):
    @abstractmethod
    def discount(self, order):
        pass


class FidelityPromo(Promotion):
    '''
    若是積分滿1000分,就能夠兌換10元現金券
    '''
    def discount(self, order):
        return 10 if order.customer.fidelity >1000 else 0

假設如今小明去商場買了一件衣服(600塊),兩雙鞋子(200*2),他的購物積分有1500點。編碼

在平時,商場通常都沒有活動,可是終年都有積分換現金抵用券的活動。

>>> from collections import namedtuple

# 定義兩個字段:名字,購物積分
>>> Customer = namedtuple('Customer', 'name fidelity')
>>> xm = Customer('小明', 1500)
>>> item1 = Item('鞋子', 200, 3)
>>> item2 = Item('衣服', 600, 1)
>>> order = Order(xm, FidelityPromo())
>>> order.add_to_cart(item1, item2)

# 原價 1200,用上積分後,只要1190
>>> order
<Order Total:1200 due:1190>

眼看着,五一節也快了,商場準備大搞促銷

  • 只要單項商品購買10件,便可9折。
  • 若是訂單總金額大於等於500,就能夠立減50。

有了此前咱們使用 策略模式 打下的基礎,咱們並非使用硬編碼的方式來配置策略,因此不須要改動太多的源碼,只要直接定義五一節的兩個促銷策略類便可(一樣繼承自 Promotion 抽象基類),就像插件同樣,即插即用。

class BulkItemPromo(Promotion):
    '''
    若是單項商品購買10件,便可9折。
    '''
    def discount(self, order):
        discount = 0
        for item in order.cart:
            if item.quantity >= 10:
                discount += item.total() * 0.1
        return discount

class LargeOrderPromo(Promotion):
    '''
    若是訂單總金額大於等於500,就能夠立減50
    '''
    def discount(self, order):
        discount = 0
        if order.total() >= 500:
            discount = 50

        return discount

看到商場活動如此給力,小明的錢包也鼓了起來,開始屯起了生活用品。

若是使用了第一個策略,原價600,只須要花 580

>>> from collections import namedtuple
>>> Customer = namedtuple('Customer', 'name fidelity')

>>> xm = Customer('小明', 300)

>>> item1 = Item('紙巾', 20, 10)
>>> item2 = Item('食用油', 50, 4)
>>> item3 = Item('牛奶', 50, 4)


>>> order = Order(xm, BulkItemPromo())
>>> order.add_to_cart(item1, item2, item3)

>>> order
<Order Total:600 due:580.0>

若是使用了第二個策略,原價600,只須要花550

>>> from collections import namedtuple
>>> Customer = namedtuple('Customer', 'name fidelity')

>>> xm = Customer('小明', 300)

>>> item1 = Item('紙巾', 20, 10)
>>> item2 = Item('食用油', 50, 4)
>>> item3 = Item('牛奶', 50, 4)


>>> order = Order(xm, LargeOrderPromo())
>>> order.add_to_cart(item1, item2, item3)

>>> order
<Order Total:600 due:550>

兩個策略即插即用,只須要在前臺下訂單時,選擇對應的策略便可,原業務邏輯無需改動。

>>> order = Order(xm, BulkItemPromo())
>>> order = Order(xm, LargeOrderPromo())

可是問題很快又來了,商場搞活動,卻讓顧客手動選擇使用哪一個優惠策略,做爲一個良心的商家,應該要能自動對比全部策略得出最優惠的價格來給到顧客。這就要求後臺代碼要可以找出當前可用的所有策略,並一一比對摺扣。

# 找出全部的促銷策略
all_promotion = [globals()[name] for name in globals() if name.endswith('Promo') and name != 'BestPromo']

# 實現一個最優策略類
class BestPromo(Promotion):
    def discount(self, order):
        # 找出當前文件中全部的策略
        all_promotion = [globals()[name] for name in globals() if name.endswith('Promo') and name != 'BestPromo']

        # 計算最大折扣
        return max([promo().discount(order) for promo in all_promotion])

在前臺下訂單的時候,就會自動計算全部的優惠策略,直接告訴顧客最便宜的價格。

# 直接選擇這個最優策略
>>> order = Order(xm, BestPromo())
>>> order.add_to_cart(item1, item2, item3)

>>> order
<Order Total:600 due:550>

經過以上例子,能夠總結出使用策略模式的好處

  1. 擴展性優秀,移植方便,使用靈活。能夠很方便擴展策略;
  2. 各個策略能夠自由切換。這也是依賴抽象類設計接口的好處之一;

但同時,策略模式 也會帶來一些弊端。

  1. 項目比較龐大時,策略可能比較多,不便於維護;
  2. 策略的使用方必須知道有哪些策略,才能決定使用哪個策略,這與迪米特法則是相違背的。

對於以上的例子,仔細一想,其實還有很多能夠優化的地方。

好比,爲了實現經典的模式,咱們先要定義一個抽象基類,再實現具體的策略類。對於上面這樣一個簡單的計算折扣價格邏輯來講,其實能夠用函數來實現,而後在實例化 Order 類時指定這個策略函數便可,大可沒必要將類給搬出來。這樣就能夠避免在下訂單時,不斷的建立策略對象,減小多餘的運行時消耗。這裏就不具體寫出代碼了。

因此學習設計模式,不只要知道如何利用這樣的模式組織代碼,更要領會其思想,活學活用,靈活變通。

以上,就是今天關於 策略模式 的一些我的分享,若有講得不到位的,還請後臺留言指正!

參考文檔

  • 《流暢的Python》

關注公衆號,獲取最新干貨!

原文出處:https://www.cnblogs.com/wongbingming/p/10753072.html

相關文章
相關標籤/搜索