案例分析:重构“策略”模式
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
如果合理利用作为一等对象的函数,某些设计模式可以简化,“策略”模式就是其中一个很好的例子。
经典的“策略”模式
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
使用“策略”设计模式处理订单折扣的 UML 类图
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
电商领域有个功能明显可以使用“策略”模式,即根据客户的属性或订单中的商品计算折扣。
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
假如一个网店制定了下述折扣规则:
- 有1000或者以上积分的客户,每个订单享5%折扣
- 同一个订单中,单个商品的数量达到20个或以上,享10%折扣
- 订单中的不同商品达到10个或以上的,享7%的折扣
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
简单起见,我们假定一个订单一次只能享用一个折扣。
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
上下文
把一些计算委托给实现不同算法的可交互组件,它提供服务。在这个点上实例中,上下文是Order,它会根据不同的算法计算促销折扣
策略
实现不同算法的组件共同的接口,在这个实例中,名为Promotion的抽象类扮演这个角色
具体策略
“策略”的具体子类。fidelityPromo、BulkPromo和LargeOrderPromo是这里实现的三个具体策略
1 from abc import ABC, abstractmethod 2 from collections import namedtuple 3 4 5 Customer = namedtuple(‘Customer‘, ‘name fidelity‘) 6 7 8 class LineItem: 9 10 def __init__(self, product, quantity, price): 11 self.product = product 12 self.quantity = quantity 13 self.price = price 14 15 def total(self): 16 return self.price * self.quantity 17 18 19 class Order: 20 21 def __init__(self, customer, cart, promotion=None): 22 self.customer = customer 23 self.cart = list(cart) 24 self.promotion = promotion 25 26 def total(self): 27 if not hasattr(self, ‘__total‘): 28 self.__total = sum(item.total() for item in self.cart) 29 return self.__total 30 31 def due(self): 32 if self.promotion is None: 33 discount = 0 34 else: 35 discount = self.promotion.discount(self) 36 return self.total() - discount 37 38 def __repr__(self): 39 fmt = ‘<Order total: {:.2f} due: {:.2f}>‘ 40 return fmt.format(self.total(), self.due()) 41 42 class Promotion(ABC): 43 44 @abstractmethod 45 def discount(self, order): 46 """返回折扣金额(正值)""" 47 48 49 class FidelityPromo(Promotion):# 第一个具体策略 50 """为积分为1000货以上的顾客提供5%的折扣""" 51 52 def discount(self, order): 53 return order.total() * .05 if order.customer.fidelity >= 1000 else 0 54 55 56 class BulkItemPromo(Promotion): # 第二个具体策略 57 """单个商品为20个或以上时提供10%折扣""" 58 59 def discount(self, order): 60 discount = 0 61 for item in order.cart: 62 if item.quantity >= 20: 63 discount += item.total() * .1 64 return discount 65 66 67 class LargeOrderPromo(Promotion): # 第三个具体策略 68 """订单中的不同商品达到10个或以上时提供7%折扣""" 69 70 def discount(self, order): 71 distinct_items = {item.product for item in order.cart} 72 if len(distinct_items) >= 10: 73 return order.total() * .07 74 return 0 75 76 77 #两个顾客:joe 的积分是 0,ann 的积分是 1100 78 joe = Customer(‘John Doe‘, 0) 79 ann = Customer(‘Ann Smith‘, 1100) 80 81 #有三个商品的购物车 82 cart = [LineItem(‘banana‘, 4, .5), 83 LineItem(‘apple‘, 10, 1.5), 84 LineItem(‘watermellon‘, 5, 5.0) 85 ] 86 87 #fidelityPromo 没给 joe 提供折扣 88 print(‘joe 0积分:‘, Order(joe, cart, FidelityPromo())) 89 #ann 得到了 5% 折扣,因为她的积分超过 1000 90 print(‘ann 1000积分:‘, Order(ann, cart, FidelityPromo())) 91 92 #banana_cart 中有 30 把香蕉和 10 个苹果 93 banana_cart = [LineItem(‘banana‘, 30, .5), 94 LineItem(‘apple‘, 10, 1.5) 95 ] 96 97 #BulkItemPromo 为 joe 购买的香蕉优惠了 1.50 美元 98 print(‘joe banana cart:‘, Order(joe, banana_cart, BulkItemPromo())) 99 100 #long_order 中有 10 个不同的商品,每个商品的价格为 1.00 美元 101 long_order = [LineItem(str(item_code), 1, 1.0) for item_code in range(10)] 102 103 #LargerOrderPromo 为 joe 的整个订单提供了 7% 折扣 104 print(‘joe 10个不同产品:‘, Order(joe, long_order, LargeOrderPromo())) 105 print(Order(joe, cart, LargeOrderPromo()))
以上代码执行的结果为:
joe 0积分: <Order total: 42.00 due: 42.00> ann 1000积分: <Order total: 42.00 due: 39.90> joe banana cart: <Order total: 30.00 due: 28.50> joe 10个不同产品: <Order total: 10.00 due: 9.30> <Order total: 42.00 due: 42.00>
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 19.5px Helvetica }
使用函数实现“策略”模式
?? Order 类和使用函数实现的折扣策略
1 #顾客名称(name)和积分(fidelity) 2 Customer = namedtuple(‘Customer‘, ‘name fidelity‘) 3 4 5 #用于处理单个产品超过10个以上的类 6 class LineItem: 7 8 def __init__(self, product, quantity, price): 9 self.product = product 10 self.quantity = quantity 11 self.price = price 12 13 def total(self): 14 # 计算单个产品的总价 15 return self.price * self.quantity 16 17 18 class Order: # 上下文 19 20 # 用于接收顾客信息(Customer)购物车信息(cart)以及需要选用的折扣(promotion) 21 def __init__(self, customer, cart, promotion=None): 22 self.customer = customer 23 self.cart = list(cart) 24 self.promotion = promotion 25 26 #计算产品的价格 27 def total(self): 28 if not hasattr(self, ‘__total‘): 29 self.__total = sum(item.total() for item in self.cart) 30 return self.__total 31 32 #计算折扣后的价格 33 def due(self): 34 if self.promotion is None: 35 discount = 0 36 else: 37 #计算折扣只需调用 self.promotion()函数 38 discount = self.promotion(self) 39 return self.total() - discount 40 41 #用法前端repr()调用的是返回结果,如果不定义__str__则默认返回__repr__ 42 def __repr__(self): 43 fmt = ‘<Order total: {:.2f} due: {:.2f}>‘ 44 return fmt.format(self.total(), self.due()) 45 46 def fidelity_promo(order): 47 """为积分为1000或以上的顾客提供5%折扣""" 48 return order.total() * .05 if order.customer.fidelity >= 1000 else 0 49 50 def bulk_item_promo(order): 51 """单个商品为20个或以上时提供10%折扣""" 52 discount = 0 53 for item in order.cart: 54 if item.quantity >= 20: 55 discount += item.total() * .1 56 return discount 57 58 def large_order_promo(order): 59 """订单中的不同商品达到10个或以上时提供7%折扣""" 60 distinct_items = {item.product for item in order.cart} 61 if len(distinct_items) >= 10: 62 return order.total() * .07 63 return 0 64 65 joe = Customer(‘John Doe‘, 0) 66 ann = Customer(‘Ann Smith‘, 1100) 67 cart = [LineItem(‘banana‘, 4, .5), 68 LineItem(‘apple‘, 10, 1.5), 69 LineItem(‘watermellon‘, 5, 5.0)] 70 71 print(Order(joe, cart, fidelity_promo)) 72 print(Order(ann, cart, fidelity_promo)) 73 74 banana_cart = [LineItem(‘banana‘, 30, .5), 75 LineItem(‘apple‘, 10, 1.5)] 76 77 print(Order(joe, banana_cart, bulk_item_promo)) 78 79 long_order = [LineItem(str(item_code), 1, 1.0) for item_code in range(10)] 80 print(Order(joe, long_order, large_order_promo)) 81 print(Order(joe, cart, large_order_promo))
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 19.5px Helvetica }
选择最佳策略:简单的方式
我们继续使用上面实例的顾客和购物车,在此基础上添加 3 个测试:
1 from collections import namedtuple 2 3 4 #顾客名称(name)和积分(fidelity) 5 Customer = namedtuple(‘Customer‘, ‘name fidelity‘) 6 7 8 #用于处理单个产品超过10个以上的类 9 class LineItem: 10 11 def __init__(self, product, quantity, price): 12 self.product = product 13 self.quantity = quantity 14 self.price = price 15 16 def total(self): 17 # 计算单个产品的总价 18 return self.price * self.quantity 19 20 21 class Order: # 上下文 22 23 # 用于接收顾客信息(Customer)购物车信息(cart)以及需要选用的折扣(promotion) 24 def __init__(self, customer, cart, promotion=None): 25 self.customer = customer 26 self.cart = list(cart) 27 self.promotion = promotion 28 29 #计算产品的价格 30 def total(self): 31 if not hasattr(self, ‘__total‘): 32 self.__total = sum(item.total() for item in self.cart) 33 return self.__total 34 35 #计算折扣后的价格 36 def due(self): 37 if self.promotion is None: 38 discount = 0 39 else: 40 #计算折扣只需调用 self.promotion()函数 41 discount = self.promotion(self) 42 return self.total() - discount 43 44 #用法前端repr()调用的是返回结果,如果不定义__str__则默认返回__repr__ 45 def __repr__(self): 46 fmt = ‘<Order total: {:.2f} due: {:.2f}>‘ 47 return fmt.format(self.total(), self.due()) 48 49 def fidelity_promo(order): 50 """为积分为1000或以上的顾客提供5%折扣""" 51 return order.total() * .05 if order.customer.fidelity >= 1000 else 0 52 53 def bulk_item_promo(order): 54 """单个商品为20个或以上时提供10%折扣""" 55 discount = 0 56 for item in order.cart: 57 if item.quantity >= 20: 58 discount += item.total() * .1 59 return discount 60 61 def large_order_promo(order): 62 """订单中的不同商品达到10个或以上时提供7%折扣""" 63 distinct_items = {item.product for item in order.cart} 64 if len(distinct_items) >= 10: 65 return order.total() * .07 66 return 0 67 68 69 promos = [fidelity_promo, bulk_item_promo, large_order_promo] 70 71 #选用最佳折扣 72 def best_promo(order): 73 return max(promo(order) for promo in promos) 74 75 joe = Customer(‘John Doe‘, 0) 76 ann = Customer(‘Ann Smith‘, 1100) 77 cart = [LineItem(‘banana‘, 4, .5), 78 LineItem(‘apple‘, 10, 1.5), 79 LineItem(‘watermellon‘, 5, 5.0)] 80 81 banana_cart = [LineItem(‘banana‘, 30, .5), LineItem(‘apple‘, 10, 1.5)] 82 83 long_order = [LineItem(str(item_code), 1, 1.0) for item_code in range(10)] 84 85 #best_promo 为顾客 joe 选择 larger_order_promo 86 print(Order(joe, long_order, best_promo)) 87 #订购大量香蕉时,joe 使用 bulk_item_promo 提供的折扣 88 print(Order(joe, banana_cart, best_promo)) 89 #在一个简单的购物车中,best_promo 为忠实顾客 ann 提供fidelity_promo 优惠的折扣 90 print(Order(ann, cart, best_promo))
以上代码执行的结果为:
<Order total: 10.00 due: 9.30> <Order total: 30.00 due: 28.50> <Order total: 42.00 due: 39.90>
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 19.5px Helvetica }
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 15.0px Helvetica }