python 策略模式

策略模式定义如下:定义一组算法,将每个算法都封装起来,并使他们之间可互换.

面向对象的方法实现一般是定义一个带抽象方法的基类, 不同子类继承基类并实现相应方法.
在调用时传入不同的子类时, 执行方法名一致而行为不一致.

以 <<流畅的 python>> 一书第六七章的部分内容总结来理解策略模式.

电商网站可以根据客户属性和订单商品来实行不同的折扣策略.

首先是面向对象的实现.

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
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 cart)
return self.__total

def due(self):
discount = self.promotion.discount(self) if self.promotion else 0
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 FidelityPromotion(Promotion):

def discount(self, order):
return order.total * 0.05 if order.customer.fidelity > 1000 else 0


class BulkItemPromotion(Promotion):

def discount(self, order):
discount = 0
for item in order.cart:
if item.quantity > 20:
discount += item.total() * 0.1
return discount

class LargeOrderPromotion(Promotion):

def discount(self, order):
distinct_items = {item.product for item in order.cart}
return order.total() * 0.07 if len(distinct_items) > 10 else 0

此时在生成订单时可以选择不同的策略来达到不同折扣的目的.

但我们也发现其实在 Order.due(self) 计算折扣时都是调用不同策略的同一个函数.
由于 python 的函数是一等公民, 所以在实现策略模式时完全可以只使用函数.

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
from abc import ABC, abstractmethod
from collections import namedtuple

Customer = namedtuple('Customer', 'name fidelity')

class LineItem:
# ...


class Order:
# ...
def due(self):
discount = self.promotion(self) if self.promotion else 0
return self.total() - discount
# ...


def fidelity_promo(order):
return order.total * 0.05 if order.customer.fidelity > 1000 else 0

def bulk_item_promo(order):
discount = 0
for item in order.cart:
if item.quantity > 20:
discount += item.total() * 0.1
return discount

def large_order_promo(order):
distinct_items = {item.product for item in order.cart}
return order.total() * 0.07 if len(distinct_items) > 10 else 0

去掉抽象类后, 此时在生成订单时传入的是不同的函数, 接收的是相同的参数, 执行不同的策略.

由此我们也可以看出 python 相对于 Java 不同的特点: 不强制使用面向对象的方法, 使用鸭子类型,
更注重协议.