Jamal的博客

Python设计模式-策略模式(Strategy pattern)

策略模式意思是说在同一个问题上,可以使用多种方法来解决,以排序问题为例,可以使用的排序算法有很多,但是每种算法的时间、空间复杂度是不一样的,所以在不同的情况下,就需要选择不同的算法进行排序运算。策略模式鼓励使用多种算法来解决一个问题,其特性是能够在运行时透明的切换算法而客户端不需要感知这个变化。

模式概述

《设计模式:可复用面向对象软件的基础》中是这样描述“策略模式”的:
定义一系列算法,把它们一一封装起来,并且是它们可以相互替换。本模式使得算法可以独立于使用它的客户端而存在

软件实例

Python中的sorted()和list.sort()就是策略模式的例子,两个函数都接受一个命名参数key,这个参数本质上是实现了一个排序策略的函数的名称

生活中的实例

电商领域有个功能可以明显使用策略模式,就是根据客户的属性或订单中的商品计算折扣。我们假设有一个网站设定了如下规则:

1
2
3
* 有1000或以上积分的顾客,每个订单享受5%折扣
* 同一订单中,单个商品的数量达到20个或以上,享受10%折扣
* 订单中的不同商品达到10个或以上,享受7%折扣

简单起见,我们假设一个订单一次只能享受一次折扣,我们画出以下的UML图:

  • 上下文:把一些计算委托给不同算法的可互换组件,他提供服务。在这个示例中上下文是Order,他会根据不同的算法计算促销折扣
  • 策略:实现不同算法的组件共同的接口。在这个示例中,Promotion这个抽象类扮演这个角色
  • 具体策略:“策略”的具体子类,这里是如图的三个实际策略

代码实现

以下是上述模式的具体实现,按照《设计模式》的说明,具体的策略由上下文类的客户端进行选择。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
from abc import ABC, abstractmethod
from collections import namedtuple
Customer = namedtuple("Customer", "name, fidelity")
class LineItem:
def __init__(self, product, quantity, price):
self.product = product
self.quantity = quantity
self.price = price
def total(self):
return self.price * self.quantity
class Order: # 上下文
def __init__(self, customer, cart, promotion=None):
self.customer = customer
self.cart = list(cart)
self.promotion = promotion
def total(self):
if not hasattr(self, "__total"):
self.__total = sum(item.total() for item in self.cart)
return self.__total
def due(self):
if self.promotion is None:
discount = 0
else:
discount = self.promotion.discount(self)
return self.total() - discount
def __repr__(self):
fmt = "<Order total: {:.2f} due: {:.2f}>"
return fmt.format(self.total(), self.due())
class Promotion(ABC):
@abstractmethod
def discount(self, order):
pass
class BulkItemPromo(Promotion):
"""单个商品为20个或以上时提供10%折扣"""
def discount(self, order):
discount = 0
for item in order.cart:
if item.quantity >= 20:
discount += item.total() * 0.1
return discount
class FideliltPromo(Promotion):
"""为积分1000以上的提供5%折扣"""
def discount(self, order):
return order.total() * 0.05 if order.customer.fidelity >= 1000 else 0
class LargeOrderPromo(Promotion):
"""订单中的不同商品达到10个或以上,享受7%折扣"""
def discount(self, order):
discount_item = {item.product for item in order.cart}
if len(discount_item) >= 10:
return order.total() * 0.07
return 0
def main():
joe = Customer("john", 0)
ann = Customer("ann", 1000)
cart = [LineItem("apple", 4, .5), LineItem("a1", 10, 1.5), LineItem("a3", 5, 5.0)]
print(Order(joe, cart, FideliltPromo()))
print(Order(ann, cart, FideliltPromo()))
if __name__ == "__main__":
main()
```
实例中是完全可用的,但是在Python中,可以使用更少的代码实现相同的功能。
在函数非一等公民的语言中,策略模式中每个策略都需要被一个不同的类来实现,在Python中,由于函数是一等公民,我们可以把函数看做是普通的变量,这样就简化了策略模式的实现,我们看如下的实现代码:
```Python
from collections import namedtuple
Customer = namedtuple("Customer", "name, fidelity")
class LineItem:
def __init__(self, product, quantity, price):
self.product = product
self.quantity = quantity
self.price = price
def total(self):
return self.price * self.quantity
class Order: # 上下文
def __init__(self, customer, cart, promotion=None):
self.customer = customer
self.cart = list(cart)
self.promotion = promotion
def total(self):
if not hasattr(self, "__total"):
self.__total = sum(item.total() for item in self.cart)
return self.__total
def due(self):
if self.promotion is None:
discount = 0
else:
discount = self.promotion(self)
return self.total() - discount
def __repr__(self):
fmt = "<Order total: {:.2f} due: {:.2f}>"
return fmt.format(self.total(), self.due())
def fidelity_promo(order):
"""为积分1000以上的提供5%折扣"""
return order.total() * 0.05 if order.customer.fidelity >= 1000 else 0
def bulk_item_promo(order):
"""单个商品为20个或以上时提供10%折扣"""
discount = 0
for item in order.cart:
if item.quantity >= 20:
discount += item.total() * 0.1
return discount
def large_order_promo(order):
"""订单中的不同商品达到10个或以上,享受7%折扣"""
discount_item = {item.product for item in order.cart}
if len(discount_item) >= 10:
return order.total() * 0.07
return 0
def main():
joe = Customer("john", 0)
ann = Customer("ann", 1000)
cart = [LineItem("apple", 4, .5), LineItem("a1", 10, 1.5), LineItem("a3", 5, 5.0)]
print(Order(joe, cart, fidelity_promo))
print(Order(ann, cart, fidelity_promo))
if __name__ == "__main__":
main()
```
使用函数完成策略模式,有以下特点:
  • 计算折扣只需调用self.promotion()函数
  • 没有抽象类
  • 每个策略都是函数
    `` 注意在这里,不需要再新建订单的时候实例化新的促销对象,函数可以直接使用。 《设计模式》中指出:策略对象通常是很好的享元(flyweight)享元的定义是:享元是可共享的对象,可以同时在多个上下文中使用`
    共享是策略模式推荐的做法,这样就不必再每个新的上下文里(这里是Order实例)中使用相同的策略时不断地新建具体的策略对象,从而减少了消耗