# coding: utf-8 # # Srategy Pattern # 策略模式定义一系列算法并封装它们,这些算法可以互换。 # 策略模式还根据不同的对象使用不同的算法。 # ## class implement # In[10]: import abc import collections Customer = collections.namedtuple('Customer', 'name fidelity') class LineItem(object): def __init__(self, product, quantity, price): self.product = product self.quantity = quantity self.price = price def total(self): return self.quantity * self.price class Order(object): def __init__(self, customer, cart, promotion=None): self.customer = customer self.cart = cart self.promotion = promotion def total(self): if not hasattr(self, '__total'): self.__total = sum(lineitem.total() for lineitem in self.cart) return self.__total def due(self): discount = 0 if self.promotion is None else self.promotion.discount(self) return self.total() - discount def __repr__(self): return "<Order total:{:.2f} due:{:.2f}>".format(self.total(), self.due()) class Promotion(abc.ABC): """abstract class of Promotion""" @abc.abstractmethod def discount(self, Order): """return the discount vary different Order """ pass class FidelityPromo(Promotion): """if fidelity >= 1000 Give 5% discount""" def discount(self, order): """ return 0.05 of the total price of Order """ return 0.05*order.total() if order.customer.fidelity >= 100 else 0.0 class BulkItemPromo(Promotion): """if the lineitem's quantity >= 20 give the lineitem'total price 10% discount""" def discount(self, order): discount = sum((0.1*lineitem.total() if lineitem.quantity >= 20 else 0.0 for lineitem in order.cart)) return discount class LargeOrderPromo(Promotion): """7% discount for the kind of lineitems in cart >10 """ def discount(self, order): kinds = {lineitem.product for lineitem in order.cart} return 0.07*order.total() if len(kinds)>=10 else 0.0 # In[3]: joe = Customer('John Doe', 0) # In[4]: ann = Customer('Ann Smith', 1100) # In[5]: cart = [LineItem('banana', 4, .5),LineItem('apple', 10, 1.5),LineItem('watermellon', 5, 5.0)] # In[11]: Order(joe, cart, FidelityPromo()) # joe's fidelity < 1000 # Out[11]: <Order total:42.00 due:42.00> # In[12]: Order(ann, cart, FidelityPromo()) # ann's fidelity > 1000 # Out[12]: <Order total:42.00 due:39.90> # In[13]: banana_cart = [LineItem('banana', 30, .5),LineItem('apple', 10, 1.5)] # In[14]: Order(joe, banana_cart, BulkItemPromo()) # number of 'banana' > 20 # Out[14]: <Order total:30.00 due:28.50> # In[15]: long_order = [LineItem(str(item_code), 1, 1.0) for item_code in range(10)] # In[16]: Order(joe, long_order, LargeOrderPromo()) # number of line items >=10 # Out[16]: <Order total:10.00 due:9.30> # In[17]: Order(joe, cart, LargeOrderPromo()) # Out[17]: <Order total:42.00 due:42.00> # ## function implement # function is the first class in python. # considering the subclasses of Promotion are very simple, i think implementation them as function is better. # the defination of Customer, LineItem and Oder is no need to change. # but change the due method in Oder is nesscery. # In[18]: import collections Customer = collections.namedtuple('Customer', 'name fidelity') class LineItem(object): def __init__(self, product, quantity, price): self.product = product self.quantity = quantity self.price = price def total(self): return self.quantity * self.price class Order(object): def __init__(self, customer, cart, promotion=None): self.customer = customer self.cart = cart self.promotion = promotion def total(self): if not hasattr(self, '__total'): self.__total = sum(lineitem.total() for lineitem in self.cart) return self.__total def due(self): discount = 0 if self.promotion is None else self.promotion(self) return self.total() - discount def __repr__(self): return "<Order total:{:.2f} due:{:.2f}>".format(self.total(), self.due()) def fidelity_promo(order): return 0.05*order.total() if order.customer.fidelity >= 100 else 0.0 def bulk_promo(order): return sum((0.1*lineitem.total() if lineitem.quantity >= 20 else 0.0 for lineitem in order.cart)) def large_order_promo(order): kinds = {lineitem.product for lineitem in order.cart} return 0.07*order.total() if len(kinds)>=10 else 0.0 # In[19]: joe = Customer('John Doe', 0) ann = Customer('Ann Smith', 1100) cart = [LineItem('banana', 4, .5),LineItem('apple', 10, 1.5),LineItem('watermellon', 5, 5.0)] # In[20]: Order(joe, cart, fidelity_promo) # joe's fidelity < 1000 # Out[20]: <Order total:42.00 due:42.00> # In[21]: Order(ann, cart, fidelity_promo) # ann's fidelity > 1000 # Out[21]:<Order total:42.00 due:39.90> # In[22]: banana_cart = [LineItem('banana', 30, .5),LineItem('apple', 10, 1.5)] # In[24]: Order(joe, banana_cart, bulk_promo) # number of 'banana' > 20 # Out[24]: <Order total:30.00 due:28.50> # In[25]: long_order = [LineItem(str(item_code), 1, 1.0) for item_code in range(10)] # In[27]: Order(joe, long_order, large_order_promo) # number of line items >=10 # Out[27]:<Order total:10.00 due:9.30> # In[28]: Order(joe, cart, large_order_promo) # Out[28]: <Order total:42.00 due:42.00> # ## what is the best promotion # In[31]: promos = [fidelity_promo, bulk_promo, large_order_promo] def best_promo(order): return max(promo(order) for promo in promos) # In[32]: Order(joe, long_order, best_promo) # Out[32]:<Order total:10.00 due:9.30> # In[33]: Order(joe, banana_cart, best_promo) # Out[33]: <Order total:30.00 due:28.50> # In[34]: Order(ann, cart, best_promo) # Out[34]:<Order total:42.00 due:39.90> # there is another way to find the all promotions in a module. # use globals() # globals() return a dict of all parameters: variable, function , class in current module. # In[35]: promos = [globals()[name] for name in globals() if name.endswith('_promo') and name != 'best_promo'] # also inspect module can inpect a module. # for example, there is a module named promotions: # In[ ]: promos = [func for name, func in inspect.getmembers(promotions, inspect.isfunction)]