7. 策略模式
7.1 是什么
定义一系列算法,把他们封装起来,并且相互可以替换。这样在不同的上下文中就可以采用不同的算法(策略)。
7.2 使用场景:
- 如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
- 一个系统需要动态地在几种算法中选择一种。
- 如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。
7.3 案例分析:重构策略模式
虽然设计模式与语言无关, 但这并不意味着每一个模式都能在每一门语言中使用。 1996年, Peter Norvig 在题为“Design Patterns in Dynamic Languages”(http://norvig.com/designpatterns/) 的演讲中指出, Gamma 等人合著的《设计模式: 可复用面向对象软件的基础》一书中有 23 个模式, 其中有 16 个在动态语言中“不见了, 或者简化了”(参见第 9 张幻灯片)。
《设计模式: 可复用面向对象软件的基础》一书采用了c++的语言特性,而python中函数也是对象,因而可以把设计模式里的某些类的实例换成简单的函数,从而简化代码。
7.3.1 经典策略模式
图 6-1 中的 UML类图指出了“策略”模式对类的编排。
电商领域的策略模式,假定一个网店指定一下折扣规则:
- 有 1000 或以上积分(fidelity)的顾客, 每个订单享 5% 折扣。
- 同一订单中, 单个商品的数量达到 20 个或以上, 享 10% 折扣。
- 订单中的不同商品达到 10 个或以上, 享 7% 折扣。
- 一个订单一次只能享用一个折扣
“策略”模式的 UML类图见图 6-1, 其中涉及下列内容。
- 上下文
提出计算需求,把计算需求委托给不同算法。它提供服务。 在这个电商示例中, 上下文是 Order,它调用了不同的算法计算折扣。 - 策略
实现不同算法的组件共同的接口。 在这个示例中, 名为 Promotion 的抽象类扮演这个角色。 - 具体策略
“策略”的具体子类。 fidelityPromo、 BulkPromo 和 LargeOrderPromo 是这里实现的三个具体策略。
按照《设计模式: 可复用面向对象软件的基础》 一书的说明, 具体策略由上下文类选择。 在这个示例中, 实例化订单之前, 系统会以某种方式选择一种促销折扣策略, 然后把它传给 Order 构造方法。 具体怎么选择策略, 不在这个模式的职责范围内。
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=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 FideliyPromo(Promotion):
def discount(self,order):
return order.total()*0.05 if order.customer.fidelity>=1000 else 0
class BulkItemPromo(Promotion):
def discount(self,order):
discount=0
for item in order.cart:
if item.quantity>=20:
discount+=item.total()*0.1
return discount
class LargeOrderPromo(Promotion):
def discount(self,order):
discount_items={item.product for item in order.cart}
if len(discount_items)>=10:
return order.total()*0.07
return 0
>>> joe = Customer('John Doe', 0)
>>> cart = [LineItem('banana', 4, .5),
... LineItem('apple', 10, 1.5),
... LineItem('watermellon', 5, 5.0)]
>>> Order(joe, cart, FidelityPromo())
<Order total: 42.00 due: 42.00>
7.3.2 使用函数实现策略模式
可以看到在上一节中策略类中除了实现了discount方法,别的什么都没有。既然如此,为什么不把类直接换成函数呢?
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=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:
return self.total()-self.promotion(self)
else:
return self.total()
def __repr__(self):
fmt='<Order total: {:.2f} due: {:.2f}>'
return fmt.format(self.total(), self.due())
def fidelity_promo(order):
return order.total()*0.05 if order.customer.fidelity>=1000 else 0
def bulk_promo(order):
discount=0
for item in order.cart:
if item.quantity>=20:
discount+=item.total()*0.1
return discount
def larg_order_promo(order):
discount_items={item.product for item in order.cart}
if len(discount_items)>=10:
return order.total()*0.07
7.4 总结
在python中之所以不需要为策略单独建一个类,是因为python中函数也是对象,可以方便地传递给调用者。
参考:《流畅的python》