符合模式不代表做得对。 ------Ralph Johnson《设计模式:可复用面向对象软件的基础》
案例分析:重构策略模式
利用一等对象的函数,能够使得某些设计模式得以简化。策略模式是个很经典的例子,能够用函数重构它来有效的精简代码。
经典的策略模式
摘自《流畅的python》
淘宝、天猫的购物功能就可以使用策略模式,用户购买商品到购物车,然后系统后台根据不同的策略给予用户一定的回扣。
假定有如下折扣规则:
* 用户积分在1000及以上,每个订单享受5%折扣
* 同一订单中,单个商品的价格达到20个及以上,享受10%折扣
* 订单中的不同商品达到10个或以上,享受7%折扣
为方便起见,不妨假定用户只能享受一种折扣。
上下文
把一些计算委托给实现不同算法的可互换组件,即Order
策略
实现不同算法的共同接口,即Promotion
具体策略
具体的不同算法,即fidelityPromo,BulkPromo,LargeOrderPromo
下面看代码
1 #导入抽象基类、抽奖方法 2 from abc import ABC, abstractmethod 3 #具名元组 4 from collections import namedtuple 5 Customer = namedtuple('Customer', 'name fidelity') 6 7 class LineItem: 8 """ 9 单件商品 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 return self.price * self.quantity 18 19 class Order: 20 def __init__(self, customer, cart, promotion): 21 self.customer = customer 22 self.cart = list(cart) 23 self.promotion = promotion 24 25 def total(self): 26 if not hasattr(self, '__total'): 27 self.__total = sum(item.total() for item in self.cart) 28 return self.__total 29 30 def due(self): 31 if self.promotion is None: 32 discount = 0 33 else: 34 #discount = self.promotion.discount(self) 35 discount = self.promotion(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 #抽象类 43 class Promotion(ABC): 44 @abstractmethod 45 def discount(self, order): 46 """ 47 返回折扣金额 48 """ 49 pass 50 51 class FidelPromotion(Promotion): 52 """ 53 为积分1000及以上的顾客提供5%的折扣 54 """ 55 def discount(self, order): 56 return order.total()*0.05 if order.customer.fidelity >= 1000 else 0 57 58 59 class BulkItemPromotion(Promotion): 60 """ 61 单个商品为20个及以上提供10%的折扣 62 """ 63 def discount(self, order): 64 discount = 0 65 for item in order.cart: 66 if item.quantity >= 20: 67 discount += item.total()*0.1 68 return discount 69 70 class LargeOrderPromotion(Promotion): 71 """ 72 订单中的不同商品达到10个及以上时提供7%折扣 73 """ 74 def discount(self, order): 75 itemset = set(item.product for item in order.cart) 76 #print(itemset) 77 return order.total()*0.07 if len(itemset) >= 10 else 0 78 79 def fidelity_promotion(order): 80 """ 81 为积分1000及以上的顾客提供5%的折扣 82 """ 83 return order.total()*0.05 if order.customer.fidelity >= 1000 else 0 84 85 86 def bulkitem_promotion(order): 87 """ 88 单个商品为20个及以上提供10%的折扣 89 """ 90 discount = 0 91 for item in order.cart: 92 if item.quantity >= 20: 93 discount += item.total()*0.1 94 return discount 95 96 def largeorder_promotion(order): 97 """ 98 订单中的不同商品达到10个及以上时提供7%折扣 99 """ 100 itemset = set(item.product for item in order.cart) 101 #print(itemset) 102 return order.total()*0.07 if len(itemset) >= 10 else 0 103 104 joe = Customer('John Doe', 0) 105 ann = Customer('Ann Smith', 1100) 106 cart = [ 107 LineItem('banana', 4, .5), 108 LineItem('apple', 10, 1.5), 109 LineItem('watermellon', 5, 5.0) 110 ] 111 112 print(Order(joe, cart, FidelPromotion())) 113 print(Order(ann, cart, FidelPromotion()))
Promotion定义为抽奖类,使用@abstractmethod修饰discount方法,所以子类FidelityPromotion,BulkItemPromotion,LargeOrderPromotion必须要实现discount方法。
使用函数实现策略模式
在上面的每个策略都是一个类,且只实现了discount一个方法,而且没有实例属性(如果有,可以考虑闭包),因此我们可以定义函数取代上面的策略类。
1 def fidelity_promotion(order): 2 """ 3 为积分1000及以上的顾客提供5%的折扣 4 """ 5 return order.total()*0.05 if order.customer.fidelity >= 1000 else 0 6 7 8 def bulkitem_promotion(order): 9 """ 10 单个商品为20个及以上提供10%的折扣 11 """ 12 discount = 0 13 for item in order.cart: 14 if item.quantity >= 20: 15 discount += item.total()*0.1 16 return discount 17 18 def largeorder_promotion(order): 19 """ 20 订单中的不同商品达到10个及以上时提供7%折扣 21 """ 22 itemset = set(item.product for item in order.cart) 23 #print(itemset) 24 return order.total()*0.07 if len(itemset) >= 10 else 0
可以看出,使用函数的方式重构策略模式,代码行数减少了,而且使用起来也更方便些。
1 print(Order(joe, cart, fidelity_promotion)) 2 print(Order(ann, cart, fidelity_promotion)) 3 4 print(Order(joe, banana_cart, bulkitem_promotion))