Python 使用一等函数实现设计模式

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python 使用一等函数实现设计模式相关的知识,希望对你有一定的参考价值。

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

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

经典的“策略”模式

技术分享

使用“策略”设计模式处理订单折扣的 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>

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

??  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))

选择最佳策略:简单的方式 

我们继续使用上面实例的顾客和购物车,在此基础上添加 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>

 

以上是关于Python 使用一等函数实现设计模式的主要内容,如果未能解决你的问题,请参考以下文章

web代码片段

Go 语言设计哲学之十五:函数是一等公民

用python实现盲盒抽奖功能(减库存)

python一等函数

python技巧 一等函数

Python笔记 · First-Class Functions | 函数是“一等”的