文章目錄
- 函數式編程進階:用函數實現設計模式
- 案例實現:構建“策略”模式
- 使用函數實現”策略“模式
- 享元
- 選擇最佳策略:簡單的方式
- globals關鍵字
函數式編程進階:用函數實現設計模式
案例實現:構建“策略”模式
策略模式:我們把一系列算法封裝起來,使得他們可以相互替換,本模式可以獨立于他們的客戶而變化
from abc import ABC, abstractmethod
from collections import namedtupleCustomer = namedtuple("Customer",'name fidelity')class LineItem:def __init__(self,product,quantity,price) -> None:self.product = productself.quantity = quantityself.price = pricedef total(self):return self.quantity * self.priceclass Order: # 上下文def __init__(self, customer, cart, promotion=None) -> 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) -> str:fmt = '<Order total: {:.2f} due: {:.2f}'return fmt.format(self.total,self.due)class Promotion(ABC): #抽象基類@abstractmethoddef discount(self, order):"""返回折扣金額"""class FidelityPromo(Promotion):def discount(self, order):"""積分為1000以上的顧客提供5%的折扣"""return order.total() * .05 if order.customer.fidelity >= 1000 else 0class BulkItemPromo(Promotion):def discount(self,order):"""單個商品為20個或以上時提供10%折扣"""discount = 0 for item in order.cart:if item.quantity >= 20:discount += item.total() * .10return discountclass LargeOrderPromo(Promotion):"""訂單中的不同商品達到10個以上時提供7%折扣"""def discount(self,order):discount = 0for item in order.cart:if item.quantity >= 10:discount += item.total() * .07return discount
這個實例中我們實例化了所有的策略,還有客戶訂單,使用抽象基類和抽象方法裝飾符來明確所用的方式。
測試以上用例
joe = Customer('John Doe', 0)
ann = Customer('Ann Smith',1000)
cart = [LineItem('banana',4,.5),LineItem('apple',10,1.5),LineItem('watermelon',5,5.0)]order_joe = Order(joe,cart,FidelityPromo())
order_ann = Order(ann,cart,FidelityPromo())
print(repr(order_ann))
print(repr(order_joe))
# 輸出
# <Order total: 42.00 due: 39.90>
# <Order total: 42.00 due: 42.00>
使用函數實現”策略“模式
以上實例都是基于類實現的,而且每個類都只定義了一個方法,而且每個實例都沒有自己的狀態,看起來和普通的函數沒有區別
我們可以把具體策略換成函數實現
from abc import ABC, abstractmethod
from collections import namedtupleCustomer = namedtuple("Customer",'name fidelity')class LineItem:def __init__(self,product,quantity,price) -> None:self.product = productself.quantity = quantityself.price = pricedef total(self):return self.quantity * self.priceclass Order: # 上下文def __init__(self, customer, cart, promotion=None) -> 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) -> str:fmt = '<Order total: {:.2f} due: {:.2f}>'return fmt.format(self.total(),self.due())def fidelity_promo(order):"""積分大于1000給予5%的折扣"""return order.total() * .05 if order.customer.fidelity >= 1000 else 0def bulk_item_promo(order):"""單個商品20個以上提供10%的折扣"""discount = 0 for item in order.cart:if item.quantity >= 20:discount += item.total() * .1return discountdef large_order_promo(order):"""訂單中不同商品的個數達到10個以上時提供7%的折扣"""distinct_item = {item.product for item in order.cart}if len(distinct_item >= 10):return order.total() * .07return 0joe = Customer('John Doe', 0)
ann = Customer('Ann Smith',1000)
cart = [LineItem('banana',4,.5),LineItem('apple',10,1.5),LineItem('watermelon',5,5.0)]order_joe = Order(joe,cart,fidelity_promo)
order_ann = Order(ann,cart,fidelity_promo)
print(repr(order_ann))
print(repr(order_joe))
把折扣策略通過函數實現可以減少一部分的代碼量,但是以上兩種辦法,都沒有辦法實現最佳調用方法,它們缺少內部狀態
這些具體策略都沒有內部狀態,只是單純的對上下文進行處理
這個時候需要引入享元的做法
享元
享元是可以共享的對象,同時可以在多個上下文中使用,這樣不必再新的上下文中根據不同策略不斷創建新的實例對象
選擇最佳策略:簡單的方式
promos = [fidelity_promo,bulk_item_promo,large_order_promo]
def best_promo(order):return max(promo(order) for promo in promos)
以上代碼可用但是如果添加新的方法就需要把他加到promos列表中否則best_promo函數不會考慮新的策略,要如何保證新加的策略立刻就能應用到bestpromo函數呢
globals關鍵字
globals()是python的一個內置方法,表示當前的全局符號表.
比如當我在程序運行最后打印這個關鍵字,其返回值是一個字典
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <_frozen_importlib_external.SourceFileLoader object at 0x00000234CBDD6CD0>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, '__file__': 'c:\\Users\\Administrator\\GithubRepo\\study_recording\\fluent_python\\ch06_07\\functional_pattern_design.py', '__cached__': None, 'ABC': <class 'abc.ABC'>, 'abstractmethod': <function abstractmethod at 0x00000234CC2780D0>, 'namedtuple': <function namedtuple at 0x00000234CC466550>, 'Customer': <class '__main__.Customer'>, 'LineItem': <class '__main__.LineItem'>, 'Order': <class '__main__.Order'>, 'fidelity_promo':
<function fidelity_promo at 0x00000234CC486DC0>, 'bulk_item_promo': <function bulk_item_promo at 0x00000234CC4881F0>, 'large_order_promo': <function large_order_promo at 0x00000234CC488280>, 'promos': [<function fidelity_promo at 0x00000234CC486DC0>, <function bulk_item_promo at 0x00000234CC4881F0>, <function large_order_promo at 0x00000234CC488280>], 'best_promo': <function best_promo at 0x00000234CC488310>, 'joe': Customer(name='John Doe', fidelity=0), 'ann': Customer(name='Ann Smith', fidelity=1000), 'cart': [<__main__.LineItem object at 0x00000234CC281430>, <__main__.LineItem object at 0x00000234CC2D66A0>, <__main__.LineItem object at 0x00000234CC2D68E0>], 'banana_cart': [<__main__.LineItem object at 0x00000234CC2D62E0>, <__main__.LineItem object at 0x00000234CC2D63A0>], 'order_joe': <Order total: 42.00 due: 42.00>, 'order_ann': <Order total: 42.00 due: 39.90>, 'banana_order_joe': <Order total: 30.00 due: 28.50>, 'banana_order_ann': <Order total: 30.00 due: 28.50>}
可以利用這個全局符號表來幫我們找到一些剛創建的策略
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)