详解Python设计模式之策略模式


Posted in Python onJune 15, 2020

虽然设计模式与语言无关,但这并不意味着每一个模式都能在每一门语言中使用。《设计模式:可复用面向对象软件的基础》一书中有 23 个模式,其中有 16 个在动态语言中“不见了,或者简化了”。

1、策略模式概述

策略模式:定义一系列算法,把它们一一封装起来,并且使它们之间可以相互替换。此模式让算法的变化不会影响到使用算法的客户。

电商领域有个使用“策略”模式的经典案例,即根据客户的属性或订单中的商品计算折扣。

假如一个网店制定了下述折扣规则。

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

简单起见,我们假定一个订单一次只能享用一个折扣。

UML类图如下:

详解Python设计模式之策略模式

Promotion 抽象类提供了不同算法的公共接口,fidelityPromo、BulkPromo 和 LargeOrderPromo 三个子类实现具体的“策略”,具体策略由上下文类的客户选择。

在这个示例中,实例化订单(Order 类)之前,系统会以某种方式选择一种促销折扣策略,然后把它传给 Order 构造方法。具体怎么选择策略,不在这个模式的职责范围内。(选择策略可以使用工厂模式。)

2、传统方法实现策略模式:

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 = '<订单 总价: {:.2f} 实付: {:.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() * 0.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() * 0.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() * 0.07
 return 0


joe = Customer('John Doe', 0)
ann = Customer('Ann Smith', 1100)

cart = [LineItem('banana', 4, 0.5),
 LineItem('apple', 10, 1.5),
 LineItem('watermellon', 5, 5.0)]

print('策略一:为积分为1000或以上的顾客提供5%折扣')
print(Order(joe, cart, FidelityPromo()))
print(Order(ann, cart, FidelityPromo()))

banana_cart = [LineItem('banana', 30, 0.5),
  LineItem('apple', 10, 1.5)]

print('策略二:单个商品为20个或以上时提供10%折扣')
print(Order(joe, banana_cart, BulkItemPromo()))

long_order = [LineItem(str(item_code), 1, 1.0) for item_code in range(10)]

print('策略三:订单中的不同商品达到10个或以上时提供7%折扣')
print(Order(joe, long_order, LargeOrderPromo()))
print(Order(joe, cart, LargeOrderPromo()))

输出:

策略一:为积分为1000或以上的顾客提供5%折扣
<订单 总价: 42.00 实付: 42.00>
<订单 总价: 42.00 实付: 39.90>
策略二:单个商品为20个或以上时提供10%折扣
<订单 总价: 30.00 实付: 28.50>
策略三:订单中的不同商品达到10个或以上时提供7%折扣
<订单 总价: 10.00 实付: 9.30>
<订单 总价: 42.00 实付: 42.00>

3、使用函数实现策略模式

在传统策略模式中,每个具体策略都是一个类,而且都只定义了一个方法,除此之外没有其他任何实例属性。它们看起来像是普通的函数一样。的确如此,在 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 = '<订单 总价: {:.2f} 实付: {:.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


joe = Customer('John Doe', 0)
ann = Customer('Ann Smith', 1100)

cart = [LineItem('banana', 4, 0.5),
 LineItem('apple', 10, 1.5),
 LineItem('watermellon', 5, 5.0)]

print('策略一:为积分为1000或以上的顾客提供5%折扣')
print(Order(joe, cart, fidelity_promo))
print(Order(ann, cart, fidelity_promo))

banana_cart = [LineItem('banana', 30, 0.5),
  LineItem('apple', 10, 1.5)]

print('策略二:单个商品为20个或以上时提供10%折扣')
print(Order(joe, banana_cart, bulk_item_promo))

long_order = [LineItem(str(item_code), 1, 1.0) for item_code in range(10)]

print('策略三:订单中的不同商品达到10个或以上时提供7%折扣')
print(Order(joe, long_order, large_order_promo))
print(Order(joe, cart, large_order_promo))

其实只要是支持高阶函数的语言,就可以如此实现,例如 C# 中,可以用委托实现。只是如此实现反而使代码变得复杂不易懂。而 Python 中,函数天然就可以当做参数来传递。

值得注意的是,《设计模式:可复用面向对象软件的基础》一书的作者指出:“策略对象通常是很好的享元。” 享元是可共享的对象,可以同时在多个上下文中使用。共享是推荐的做法,这样不必在每个新的上下文(这里是 Order 实例)中使用相同的策略时不断新建具体策略对象,从而减少消耗。因此,为了避免 [策略模式] 的运行时消耗,可以配合 [享元模式] 一起使用,但这样,代码行数和维护成本会不断攀升。

在复杂的情况下,需要具体策略维护内部状态时,可能需要把“策略”和“享元”模式结合起来。但是,具体策略一般没有内部状态,只是处理上下文中的数据。此时,一定要使用普通的函数,别去编写只有一个方法的类,再去实现另一个类声明的单函数接口。函数比用户定义的类的实例轻量,而且无需使用“享元”模式,因为各个策略函数在 Python 编译模块时只会创建一次。普通的函数也是“可共享的对象,可以同时在多个上下文中使用”。

以上就是详解Python设计模式之策略模式的详细内容,更多关于Python 策略模式的资料请关注三水点靠木其它相关文章!

Python 相关文章推荐
Python使用matplotlib简单绘图示例
Feb 01 Python
pytorch: tensor类型的构建与相互转换实例
Jul 26 Python
python数据处理 根据颜色对图片进行分类的方法
Dec 08 Python
在Pycharm中执行scrapy命令的方法
Jan 16 Python
Python 移动光标位置的方法
Jan 20 Python
Python之时间和日期使用小结
Feb 14 Python
Pytorch实现GoogLeNet的方法
Aug 18 Python
Python测试线程应用程序过程解析
Dec 31 Python
Python timeit模块的使用实践
Jan 13 Python
在python中使用nohup命令说明
Apr 16 Python
解决django 向mysql中写入中文字符出错的问题
May 18 Python
python实现简单贪吃蛇游戏
Sep 29 Python
python能做哪方面的工作
Jun 15 #Python
python实现二分类和多分类的ROC曲线教程
Jun 15 #Python
python属于解释型语言么
Jun 15 #Python
python要安装在哪个盘
Jun 15 #Python
python中wheel的用法整理
Jun 15 #Python
keras绘制acc和loss曲线图实例
Jun 15 #Python
Python定义一个函数的方法
Jun 15 #Python
You might like
PHP整合PayPal支付
2015/06/11 PHP
Yii数据模型中rules类验证器用法分析
2016/07/15 PHP
Javascript技术技巧大全(五)
2007/01/22 Javascript
html数组字符串拼接的最快方法
2009/09/16 Javascript
服务器端的JavaScript脚本 Node.js 使用入门
2012/03/07 Javascript
IE6下拉框图层问题探讨及解决
2014/01/03 Javascript
jQuery超简单选项卡完整实例
2015/09/26 Javascript
为何JS操作的href都是javascript:void(0);呢
2015/11/12 Javascript
jQuery toggle 代替方法
2016/03/22 Javascript
JavaScript实现定时页面跳转功能示例
2017/02/14 Javascript
JavaScript引用类型Function实例详解
2018/08/09 Javascript
vuejs数据超出单行显示更多,点击展开剩余数据实例
2019/05/05 Javascript
Typescript的三种运行方式(小结)
2019/09/18 Javascript
Vue执行方法,方法获取data值,设置data值,方法传值操作
2020/08/05 Javascript
[01:02:26]DOTA2-DPC中国联赛 正赛 SAG vs RNG BO3 第二场 1月18日
2021/03/11 DOTA
理解Python中函数的参数
2015/04/27 Python
python僵尸进程产生的原因
2017/07/21 Python
python3实现163邮箱SMTP发送邮件
2018/05/22 Python
Python动态导入模块的方法实例分析
2018/06/28 Python
Python Grid使用和布局详解
2018/06/30 Python
Python 实现数据结构中的的栈队列
2019/05/16 Python
pyinstaller 3.6版本通过pip安装失败的解决办法(推荐)
2020/01/18 Python
Python datetime 格式化 明天,昨天实例
2020/03/02 Python
10个python爬虫入门基础代码实例 + 1个简单的python爬虫完整实例
2020/12/16 Python
详解css3 mask遮罩实现一些特效
2018/10/24 HTML / CSS
如何使用amaze ui的分页样式封装一个通用的JS分页控件
2020/08/21 HTML / CSS
英国最大的在线运动补充剂商店:Discount Supplements
2017/06/03 全球购物
数据库什么时候应该被重组
2012/11/02 面试题
几个Shell Script面试题
2014/04/18 面试题
《美丽的公鸡》教学反思
2014/02/25 职场文书
我与祖国共奋进演讲稿
2014/09/13 职场文书
个人对照检查材料思想汇报(四风问题)
2014/09/25 职场文书
2015新员工试用期工作总结
2014/12/12 职场文书
投标承诺函范文
2015/01/21 职场文书
大学生旷课检讨书1000字
2015/02/19 职场文书
张丽莉事迹观后感
2015/06/16 职场文书