"一等函数"这个词其实有点坑,听起来有点像特指某一部分函数,但是其实所有函数都是一等函数。
编程语言理论家把“一等对象”定义为满足下述条件的程序实体:
- 在运行时创建
- 能赋值给变量或数据结构的元素
- 能作为参数传给函数
- 能作为函数的返回结果
---《流畅的Python》第5章 一等函数
所以可以看出,所有函数都是一等对象,所以叫做“一等函数”。
策略模式解决的问题
大多数时候我们面对的问题都有许多种处理方法。比如我上周去上海出差,我可以坐飞机去,可以坐动车去,也可以开车去,每种方法有各自的特点。在这种场景下,怎么到上海就是面对的问题,不同的交通工具就是处理办法,专业一点的说法就叫策略。策略模式就是解决这种根据不同条件,选择不同策略的机制。
写在策略前面
设计模式的本质是在已有的方案之上发现更好的方案,而不是全新的发明
这句话我想经常在脑子里过一下,提醒我在新写一个功能时,不要一上来就急着用设计模式。策略模式解决的问题场景用普通逻辑来实现大致是这样
def 去上海():
if "赶时间并且去机场方便":
做飞机去
...
elif "只有火车票票才能报销":
坐火车去
...
elif "出差顺便自驾游":
开车去
...
else:
暂时不支持的策略
策略模式的结构
策略模式由三个部分组成:
- 上下文:把一些计算委托给实现不同算法的可互换组件(具体策略),它提供服务
- 策略:实现不同算法的组件共同的接口
- 具体策略:策略的子类,策略的接口的具体实现
类实现策略模式
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):
""" 返回折扣金额(正值) """
class FidelityPromo(Promotion):
# 第一个具体策略:为积分为1000或以上的顾客提供5%折扣
def discount(self, order):
return order.total() * .05 if order.customer.fidelity >= 1000 else 0
class BulkItemPromo(Promotion):
# 第二个具体策略:单个商品为20个或以上提供10%折扣
def discount(self, order):
discount = 0
for item in order.cart:
if item.quantity >= 20:
discount += item.total() * .1
return discount
class LargeOrderPromo(Promotion):
# 第三个具体策略:订单中的不同商品达到10个或以上时提供7%折扣
def discount(self, order):
distinct_items = {item.product for item in order.cart}
if len(distinct_items) >= 10:
return order.total() * .07
return 0
if __name__ == '__main__':
joe = Customer('John Doe', 0)
ann = Customer("Ann Smith", 1100)
cart = [LineItem("banana", 4, .5),
LineItem("apple", 10, 1.5),
LineItem("watermellon", 5, 5.0)]
print(Order(joe, cart, FidelityPromo()))
print(Order(ann, cart, FidelityPromo()))
banana_cart = [LineItem("banana", 30, .5),
LineItem("apple", 10, 1.5)]
print(Order(joe, banana_cart, BulkItemPromo()))
long_cart = [LineItem(str(item_code), 1, 1.0) for item_code in range(10)]
print(Order(joe, long_cart, LargeOrderPromo()))
print(Order(joe, cart, LargeOrderPromo()))
比较不同实现:函数&类
如果具体策略比较复杂,那么使用类来实现并没有什么问题。但是假如具体策略只有一个方法,那么采用函数来实现也是一个不错的选择
使用函数实现策略模式的优点是:
- 不用再为具体策略创建抽象基类
- 不用在运行时创建具体策略类的实例对象
- 函数对象比自定义的类的实例对象更轻量
- 不需要使用享元模式,因为函数本身就是享元
具体实现
场景:Customer表示客户,LineItem类表示购物车内的产品,Order表示客户的订单,*_promo表示具体策略,用来计算不同场景下的折扣,best_promo用来自动选择折扣力度最大的具体策略。
# 策略模式(函数实现)
import inspect
import promotions
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(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() * .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() * .1
return discount
def large_order_promo(order):
""" 订单中不同商品达到10个或以上时提供7%折扣 """
distinct_items = {item.product for item in order.cart}
if len(distinct_items) >= 10:
return order.total() * .07
return 0
# promos = [fidelity_promo, bulk_item_promo, large_order_promo]
# promos = [globals()[name] for name in globals() if name.endswith("_promo")]
promos = [func for name, func in inspect.getmembers(promotions, inspect.isfunction)]
def best_promo(order):
""" 选择可用的最佳折扣 """
return max(promo(order) for promo in promos)
if __name__ == '__main__':
joe = Customer('John Doe', 0)
ann = Customer("Ann Smith", 1100)
cart = [LineItem("banana", 4, .5),
LineItem("apple", 10, 1.5),
LineItem("watermellon", 5, 5.0)]
print(Order(joe, cart, fidelity_promo))
print(Order(ann, cart, fidelity_promo))
banana_cart = [LineItem("banana", 30, .5),
LineItem("apple", 10, 1.5)]
print(Order(joe, banana_cart, bulk_item_promo))
long_cart = [LineItem(str(item_code), 1, 1.0) for item_code in range(10)]
print(Order(joe, long_cart, large_order_promo))
print(Order(joe, cart, large_order_promo))
print(Order(joe, long_cart, best_promo))
print(Order(joe, banana_cart, best_promo))
print(Order(ann, cart, best_promo))
再定义promotions.py,其中的具体策略和上面是一样的,这里表示的是一种自动获取具体策略的方法
def fidelity_promo(order):
""" 为积分为1000或以上的顾客提供5%折扣 """
return order.total() * .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() * .1
return discount
def large_order_promo(order):
""" 订单中不同商品达到10个或以上时提供7%折扣 """
distinct_items = {item.product for item in order.cart}
if len(distinct_items) >= 10:
return order.total() * .07
return 0