文章目錄
- 6.1.1 經典的“策略”模式
- 6.1.2 使用函數實現“策略”模式
- 6.1.3 選擇最佳策略:簡單的
- 6.1.4 找出模塊中的全部
- 6.2 “命令”模式
6.1.1 經典的“策略”模式
為抽象基類(Abstract Base Class,ABC),這么做是為了使用 @abstractmethod 裝飾器
來個例子:
from abc import ABC, abstractmethod
from collections import namedtuple
Customer = namedtuple('Customer', 'name fidelity')
class LineItem:def __init__(self, product, quantity, price):self.product = productself.quantity = quantityself.price = pricedef total(self):return self.price * self.quantityclass Order: #上下文def __init__(self, customer, cart, promotion=None):self.customer = customerself.cart = list(cart)self.promotion = promotiondef total(self):if not hasattr(self, '__total'):self.__total = sum(item.total() for item in self.cart)return self.__totaldef due(self):if self.promotion is None:discount = 0else:discount = self.promotion.discount(self)return self.total() - discountdef __repr__(self):fmt = '<Order total: {:.2f} due: {:.2f}>'return fmt.format(self.total(), self.due())
class Promotion (ABC): #抽象基類@abstractmethoddef discount(self, order):""""返回折扣金額"""class FidelityPromo(Promotion):#第一個具體策略"""為積分為1000或者以上的的顧客提供5%折扣"""def discount(self, order):return order.total() * 0.05 if order.customer.fidelity >= 1000 else 0class BulkItemPromo(Promotion):#第二個具體策略"""單個商品為20個或以上時提供10%折扣"""def discount(self, order):discount = 0for item in order.cart:if item.quantity >= 20:discount += item.total() *0.1return discountclass LargeOrderPromo(Promotion):#第三個具體策略"""訂單中的不同商品達到10個或者以上時提供7%折扣"""def discount(self, order):distinct_items = {item.product for item in order.cart}if len(distinct_items) >= 10:return order.total() * 0.07return 0if __name__ == '__main__':joe = Customer('John Doe', 0)ann = Customer('Ann Smith', 1000)cart = [LineItem('banana', 4, 0.5),LineItem('apple', 10, 1.5),LineItem('watermellon', 5, 5.0)]print(Order(joe, cart, FidelityPromo())) # <Order total: 42.00 due: 42.00>print(Order(ann, cart, FidelityPromo())) #<Order total: 42.00 due: 39.90>banana_cart = [LineItem('banana', 30, 0.5),LineItem('apple', 10, 1.5)]print(Order(joe, banana_cart, BulkItemPromo())) #<Order total: 30.00 due: 28.50>long_order = [LineItem(str(item_code), 1, 1.0) for item_code in range(10)]print(Order(joe, long_order, LargeOrderPromo())) #<Order total: 10.00 due: 9.30>print(Order(joe, cart, LargeOrderPromo())) #<Order total: 42.00 due: 42.00>
上下文 - 把一些計算委托給實現不同算法的可互換組件,它提供服務。在這個電商示例中,上下文是 Order,它會根據不同的算法計算促銷折扣。
策略 - 實現不同算法的組件共同的接口。在這個示例中,名為 Promotion 的抽象類扮演這
個角色。
具體策略 - “策略”的具體子類。fidelityPromo、BulkPromo 和 LargeOrderPromo 是這里實現
的三個具體策略。
6.1.2 使用函數實現“策略”模式
這個例子是對上面的例子 的重構,把具體策略換成了簡單的函數,而且去掉了Promotion 抽象類
from abc import ABC, abstractmethod
from collections import namedtuple
Customer = namedtuple('Customer', 'name fidelity')
class LineItem:def __init__(self, product, quantity, price):self.product = productself.quantity = quantityself.price = pricedef total(self):return self.price * self.quantityclass Order: #上下文def __init__(self, customer, cart, promotion=None):self.customer = customerself.cart = list(cart)self.promotion = promotiondef total(self):if not hasattr(self, '__total'):self.__total = sum(item.total() for item in self.cart)return self.__totaldef due(self):if self.promotion is None:discount = 0else:discount = self.promotion(self)return self.total() - discountdef __repr__(self):fmt = '<Order total: {:.2f} due: {:.2f}>'return fmt.format(self.total(), self.due())def fidelity_promo(order):"""為積分為1000或者以上的的顧客提供5%折扣"""return order.total() * 0.05 if order.customer.fidelity >= 1000 else 0def bulk_item_promo(order):"""單個商品為20個或以上時提供10%折扣"""discount = 0for item in order.cart:if item.quantity >= 20:discount += item.total() *0.1return discountdef large_order_promo(order):"""訂單中的不同商品達到10個或以上時提供7%折扣"""distinct_items = {item.product for item in order.cart}if len(distinct_items) >= 10:return order.total() * 0.07return 0if __name__ == '__main__':joe = Customer('John Doe', 0)ann = Customer('Ann Smith', 1000)cart = [LineItem('banana', 4, 0.5),LineItem('apple', 10, 1.5),LineItem('watermellon', 5, 5.0)]print(Order(joe, cart, fidelity_promo)) # <Order total: 42.00 due: 42.00>print(Order(ann, cart, fidelity_promo)) #<Order total: 42.00 due: 39.90>banana_cart = [LineItem('banana', 30, 0.5),LineItem('apple', 10, 1.5)]print(Order(joe, banana_cart, bulk_item_promo)) #<Order total: 30.00 due: 28.50>long_order = [LineItem(str(item_code), 1, 1.0) for item_code in range(10)]print(Order(joe, long_order, large_order_promo)) #<Order total: 10.00 due: 9.30>print(Order(joe, cart, large_order_promo)) #<Order total: 42.00 due: 42.00>
? 計算折扣只需調用 self.promotion() 函數。
? 沒有抽象類。
? 各個策略都是函數。
注意示例子中的標注:沒必要在新建訂單時實例化新的促銷對象,函數拿來即用。
- best_promo 函數計算所有折扣,并返回額度最大的
6.1.3 選擇最佳策略:簡單的
from abc import ABC, abstractmethod
from collections import namedtuple
Customer = namedtuple('Customer', 'name fidelity')
class LineItem:def __init__(self, product, quantity, price):self.product = productself.quantity = quantityself.price = pricedef total(self):return self.price * self.quantityclass Order: #上下文def __init__(self, customer, cart, promotion=None):self.customer = customerself.cart = list(cart)self.promotion = promotiondef total(self):if not hasattr(self, '__total'):self.__total = sum(item.total() for item in self.cart)return self.__totaldef due(self):if self.promotion is None:discount = 0else:discount = self.promotion(self)return self.total() - discountdef __repr__(self):fmt = '<Order total: {:.2f} due: {:.2f}>'return fmt.format(self.total(), self.due())def fidelity_promo(order):"""為積分為1000或者以上的的顧客提供5%折扣"""return order.total() * 0.05 if order.customer.fidelity >= 1000 else 0def bulk_item_promo(order):"""單個商品為20個或以上時提供10%折扣"""discount = 0for item in order.cart:if item.quantity >= 20:discount += item.total() *0.1return discountdef large_order_promo(order):"""訂單中的不同商品達到10個或以上時提供7%折扣"""distinct_items = {item.product for item in order.cart}if len(distinct_items) >= 10:return order.total() * 0.07return 0promos = [fidelity_promo,bulk_item_promo, large_order_promo]
def best_promo(order):"""選擇可用的最佳折扣"""return max(promo(order) for promo in promos)if __name__ == '__main__':joe = Customer('John Doe', 0)ann = Customer('Ann Smith', 1000)cart = [LineItem('banana', 4, 0.5),LineItem('apple', 10, 1.5),LineItem('watermellon', 5, 5.0)]# print(Order(joe, cart, fidelity_promo)) # <Order total: 42.00 due: 42.00># print(Order(ann, cart, fidelity_promo)) #<Order total: 42.00 due: 39.90>banana_cart = [LineItem('banana', 30, 0.5),LineItem('apple', 10, 1.5)]# print(Order(joe, banana_cart, bulk_item_promo)) #<Order total: 30.00 due: 28.50>long_order = [LineItem(str(item_code), 1, 1.0) for item_code in range(10)]# print(Order(joe, long_order, large_order_promo)) #<Order total: 10.00 due: 9.30># print(Order(joe, cart, large_order_promo)) #<Order total: 42.00 due: 42.00>print(Order(joe, long_order, best_promo))print(Order(joe, banana_cart, best_promo))print(Order(ann, cart, best_promo))
6.1.4 找出模塊中的全部
在 Python 中,模塊也是一等對象,而且標準庫提供了幾個處理模塊的函數。Python 文檔
是這樣說明內置函數 globals 的。
globals() 函數會以字典類型返回當前位置的全部全局變量
例子:
a= "hello"
print(globals())
#{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x7f0272b221d0>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': '/home/maxzhang/PycharmProjects/pythoncode/test.py', '__cached__': None, 'a': 'hello'}
下面的實例 使用 globals 函數幫助 best_promo 自動找到其他可用的 *_promo 函數,過程
有點曲折。
promos = [globals()[name] for name in globals() if name.endswith('_promo') and name != 'best_promo']
def best_promo(order):"""選擇可用的最佳折扣"""return max(promo(order) for promo in promos)
收集所有可用促銷的另一種方法是,在一個單獨的模塊中保存所有策略函數,把
best_promo 排除在外。
在示例 6-8 中,最大的變化是內省名為 promotions 的獨立模塊,構建策略函數列表。注意,示例 6-8 要導入 promotions 模塊,以及提供高階內省函數的 inspect 模塊(簡單起見,這里沒有給出導入語句,因為導入語句一般放在文件頂部)。
示例 6-8 內省單獨的 promotions 模塊,構建 promos 列表,inspect模塊用于收集python對象的信息,可以獲取類或函數的參數的信息,源碼,解析堆棧,對對象進行類型檢查等等。
import inspect
promos = [func for name, func ininspect.getmembers(promotions, inspect.isfunction)]
def best_promo(order):"""選擇可用的最佳折扣"""return max(promo(order) for promo in promos)
inspect.getmembers 函數用于獲取對象(這里是 promotions 模塊)的屬性,第二個
參數是可選的判斷條件(一個布爾值函數)。我們使用的是 inspect.isfunction,只
獲取模塊中的函數。
動態收集促銷折扣函數更為顯式的一種方案是使用簡單的裝飾器。第 7 章討論函數裝飾器
時會使用其他方式實現這個電商“策略”模式示例。
6.2 “命令”模式
“命令”設計模式也可以通過把函數作為參數傳遞而簡化。