# 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)]