Python设计模式中的策略模式详解
lijiachang8 人气:0策略模式
策略模式是一个经典的模式,简化代码。
电商领域有个功能明细可以使用“策略”模式,就是根据客户的属性或订单中的商品计算折扣。
比如一个网店,指定了以下的折扣规则, 并且一个订单只能享受一个折扣:
- 有1000积分以上的顾客,整个订单可以享受5%的折扣
- 同一个订单中,单个商品的数量达到20个以上,单品享受10%折扣
- 订单中不同商品的数量达到10个以上,整个订单享受7%折扣
下面是UML类图:
上下文:把一些计算委托给实现不同算法的可互换组建,他们提供服务。 在这个示例中,上下文就是Order,根据不同算法提供折扣。
策略:实现不同算法的组件共同接口。在这个示例中,Promotion的抽象类扮演这个角色。
具体策略:“策略”的子类,实现具体策略
示例,实现Order类,支持插入式折扣策略
import abc from collections import namedtuple from abc import abstractmethod Customer = namedtuple('Customer', 'name fidelity') # name:顾客名字 fidelity:忠诚度,这里用积分体现 class LineItem: """单品信息""" def __init__(self, product, quantity, price): self.product = product self.quantity = quantity self.price = price def total(self): return self.quantity * self.price 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): return '<Order total:{:.2f} due:{:.2f}>'.format(self.total(), self.due()) # {:.2f}表示输出小数点,保留2位小数 class Promotion(abc.ABC): """策略:抽象基类""" @abstractmethod def discount(self, order): """返回折扣金额""" class FidelityPromo(Promotion): """具体策略:有1000积分以上的顾客,整个订单可以享受5%的折扣""" def discount(self, order): return order.total() * 0.05 if order.customer.fidelity >= 1000 else 0 class BulkItemPromo(Promotion): """具体策略:同一个订单中,单个商品的数量达到20个以上,单品享受10%折扣""" def discount(self, order): # return order.total() * 0.1 if any(item for item in order.cart if item.quantity >= 20) else 0 理解错误为整体折扣 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): return order.total() * 0.07 if len({item.product for item in order.cart}) >= 10 else 0
聊一下抽象基类:
Python3.4中,声明抽象基类的最简单的方式是继承abc.ABC :class Promotion(abc.ABC):
Python3.0到Python3.3中,必须在class中使用metaclass=关键字 : class Promotion(metaclass=abc.ABCMeta):
Python2中,要在 类属性中增加__metaclass__ = abc.ABCMeta
测试代码
# 两个顾客,一个积分0,一个积分1100 joe = Customer('Joe', 0) ann = Customer('Ann', 1100) # 有三个商品的购物车 cart = [LineItem('banana', 4, .5), LineItem('apple', 10, 1.5), LineItem('watermelon', 5, 5.0)] # ann因为超过1000积分,获得了5%折扣 order_joe = Order(joe, cart, FidelityPromo()) #这里要FidelityPromo要加()来创建实例 print(order_joe) order_ann = Order(ann, cart, FidelityPromo()) print(order_ann) # 香蕉30个,苹果10个。 banana_cart = [LineItem('banana', 30, .5), LineItem('apple', 10, 1.5)] # joe因为香蕉有30个,根据BulkItemPromo 香蕉优惠了1.5元 order_joe = Order(joe, banana_cart, BulkItemPromo()) print(order_joe)
打印
<Order total:42.00 due:42.00>
<Order total:42.00 due:39.90>
<Order total:30.00 due:28.50>
以上的模式中,每个具体策略都是一个类,而且只定义了一个方法:discount。此外他们的策略示例没有实例属性,看起来就像普通函数。
示例,使用函数实现折扣策略
Customer = namedtuple('Customer', 'name fidelity') # name:顾客名字 fidelity:忠诚度,这里用积分体现 class LineItem: """单品信息""" def __init__(self, product, quantity, price): self.product = product self.quantity = quantity self.price = price def total(self): return self.quantity * self.price 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): return '<Order total:{:.2f} due:{:.2f}>'.format(self.total(), self.due()) # {:.2f}表示输出小数点,保留2位小数 def FidelityPromo(order): """具体策略:有1000积分以上的顾客,整个订单可以享受5%的折扣""" return order.total() * 0.05 if order.customer.fidelity >= 1000 else 0 def BulkItemPromo(order): """具体策略:同一个订单中,单个商品的数量达到20个以上,单品享受10%折扣""" discount = 0 for item in order.cart: if item.quantity >= 20: discount += item.total() * .1 return discount def LargeOrderPromo(order): """具体策略:订单中不同商品的数量达到10个以上,整个订单享受7%折扣""" return order.total() * 0.07 if len({item.product for item in order.cart}) >= 10 else 0 # 两个顾客,一个积分0,一个积分1100 joe = Customer('Joe', 0) ann = Customer('Ann', 1100) # 有三个商品的购物车 cart = [LineItem('banana', 4, .5), LineItem('apple', 10, 1.5), LineItem('watermelon', 5, 5.0)] # ann因为超过1000积分,获得了5%折扣 order_joe = Order(joe, cart, FidelityPromo) print(order_joe) order_ann = Order(ann, cart, FidelityPromo) print(order_ann) # 香蕉30个,苹果10个。 banana_cart = [LineItem('banana', 30, .5), LineItem('apple', 10, 1.5)] # joe因为香蕉有30个,根据BulkItemPromo 香蕉优惠了1.5元 order_joe = Order(joe, banana_cart, BulkItemPromo) print(order_joe)
打印
<Order total:42.00 due:42.00>
<Order total:42.00 due:39.90>
<Order total:30.00 due:28.50>
以上可以看到,使用函数更加简单,代码量减少。没必要在新建订单实例化新的促销对象,函数拿来即用。
选择最佳策略
要实现最佳折扣策略的自动选择,只需要一个额外的函数即可。这样就能自动找出最高的折扣。
def best_promotion(order): promotions = [FidelityPromo, BulkItemPromo, LargeOrderPromo] return max([func(order) for func in promotions])
自动找出模块中的全部策略
以上的promotions列表包含了三个策略,当再新增新的策略时,需要在这个列表中追加,使用以下写法,可以自动识别策略函数:
示例,实现了把Promo结尾的函数引用,放进promotions列表中
def best_promotion(order): promotions = [globals()[key] for key in list(globals().keys()) if key.endswith('Promo')] return max([func(order) for func in promotions])
以上实现的原理就是利用globals()内置函数:
globals()返回一个字典,表示当前的全局符号表。包含了当前所有定义的函数等。
自动找出模块中的全部策略-另一个种方案
把所有的策略函数,都放到一个模块(文件)中,然后通过import导入进来,再使用inspect模块提供的函数内省
示例,获取一个模块中的所有函数
import promotions # 一个包含函数的模块 print(inspect.getmembers(promotions, inspect.isfunction)) # 获取一个模块中的所有函数
打印
[('BulkItemPromo', <function BulkItemPromo at 0x0342F2B8>), ('FidelityPromo', <function FidelityPromo at 0x0342F228>), ('LargeOrderPromo', <function LargeOrderPromo at 0x0342F300>)]
示例,内省promotions模块,获取所有策略函数
import promotions promotions = [func for func_name, func in inspect.getmembers(promotions, inspect.isfunction)] def best_promotion(order): return max([func(order) for func in promotions])
这样的话,以后有新的策略,只需要在promotions模块中新增对应的策略函数,就可以自动加载了,妙!
命令模式
命令模式的目的是解耦调用者(调用操作的对象)和接收者(提供实现的对象)。让他们只实现一个方法execute接口,供调用者使用,这样调用者无需了解接受者的接口,而且不同的接受者可以适应不同的Command子类。
示例,使用抽象类实现命令模式
import abc class Receiver: """命令的接收者,执行命令的地方""" def start(self): print('开始执行') def stop(self): print('停止执行') class Command(abc.ABC): """命令抽象类""" @abc.abstractmethod def execute(self): """命令对象对外只提供execute方法""" class StartCommand(Command): """开始执行的命令""" def __init__(self, receiver): self.receiver = receiver def execute(self): return self.receiver.start() class StopCommand(Command): """停止执行的命令""" def __init__(self, receiver): self.receiver = receiver def execute(self): return self.receiver.stop() class Client: """命令的调用者""" def __init__(self, command): self.command = command def __call__(self, *args, **kwargs): return self.command.execute() start = StartCommand(Receiver()) client = Client(start) client() stop = StopCommand(Receiver()) client = Client(stop) client()
打印
开始执行
停止执行
可以不使用抽象类,直接使用一个comman()函数来代理Command示例。使用一等函数对命令模式重新审视。
加载全部内容