首页 > 技术文章 > Python 使用一等函数实现设计模式

demon89 2017-08-19 20:46 原文

案例分析:重构“策略”模式

  如果合理利用作为一等对象的函数,某些设计模式可以简化,“策略”模式就是其中一个很好的例子。

经典的“策略”模式

使用“策略”设计模式处理订单折扣的 UML 类图

电商领域有个功能明显可以使用“策略”模式,即根据客户的属性或订单中的商品计算折扣。

假如一个网店制定了下述折扣规则:

  • 有1000或者以上积分的客户,每个订单享5%折扣
  • 同一个订单中,单个商品的数量达到20个或以上,享10%折扣
  • 订单中的不同商品达到10个或以上的,享7%的折扣

简单起见,我们假定一个订单一次只能享用一个折扣。

上下文

  把一些计算委托给实现不同算法的可交互组件,它提供服务。在这个点上实例中,上下文是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>

使用函数实现“策略”模式

推荐阅读