详解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实现跨文件全局变量的方法
Jul 07 Python
在Python的Django框架中调用方法和处理无效变量
Jul 15 Python
简单的python后台管理程序
Apr 13 Python
Python实现将照片变成卡通图片的方法【基于opencv】
Jan 17 Python
Python3之读取连接过的网络并定位的方法
Apr 22 Python
python统计多维数组的行数和列数实例
Jun 23 Python
解决Python一行输出不显示的问题
Dec 03 Python
python做反被爬保护的方法
Jul 01 Python
利用setuptools打包python程序的方法步骤
Jan 18 Python
Django数据结果集序列化并展示实现过程
Apr 22 Python
Django分组聚合查询实例分享
Apr 29 Python
Python中内建模块collections如何使用
May 27 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 处理TXT文件(打开/关闭/检查/读取)
2013/05/13 PHP
解决ThinkPHP关闭调试模式时报错的问题汇总
2015/04/22 PHP
thinkPHP5 ACL用户权限模块用法详解
2017/05/10 PHP
PHP jpgraph库的配置及生成统计图表:折线图、柱状图、饼状图
2017/05/15 PHP
浅析PHP中的 inet_pton 网络函数
2019/12/16 PHP
使用Javascript和DOM Interfaces来处理HTML
2006/10/09 Javascript
一步一步教你写一个jQuery的插件教程(Plugin)
2009/09/03 Javascript
.net,js捕捉文本框回车键事件的小例子(兼容多浏览器)
2013/03/11 Javascript
基于jquery扩展漂亮的CheckBox(自己编写)
2013/11/19 Javascript
jQuery异步加载数据并添加事件示例
2014/08/24 Javascript
JavaScript错误处理
2015/02/03 Javascript
数组Array的一些方法(总结)
2017/02/17 Javascript
微信小程序wx.uploadfile 本地文件转base64的实现代码
2018/06/28 Javascript
Vue实现仿iPhone悬浮球的示例代码
2020/03/13 Javascript
Vue+Vant 图片上传加显示的案例
2020/11/03 Javascript
微信小程序对图片进行canvas压缩的方法示例详解
2020/11/12 Javascript
[03:43]TI9战队采访——PSG.LGD
2019/08/22 DOTA
python 从远程服务器下载日志文件的程序
2013/02/10 Python
Python多线程编程(七):使用Condition实现复杂同步
2015/04/05 Python
在Python的Django框架中创建语言文件
2015/07/27 Python
将Python代码嵌入C++程序进行编写的实例
2015/07/31 Python
Python验证企业工商注册码
2015/10/25 Python
使用python实现tcp自动重连
2017/07/02 Python
Python探索之ModelForm代码详解
2017/10/26 Python
python修改list中所有元素类型的三种方法
2018/04/09 Python
利用Python如何批量更新服务器文件
2018/07/29 Python
Python实现的合并两个有序数组算法示例
2019/03/04 Python
pycharm修改文件的默认打开方式的步骤
2019/07/29 Python
浅谈keras 模型用于预测时的注意事项
2020/06/27 Python
如何表示python中的相对路径
2020/07/08 Python
python 如何对logging日志封装
2020/12/02 Python
python 进制转换 int、bin、oct、hex的原理
2021/01/13 Python
应届生会计求职信
2013/11/11 职场文书
工程管理专业个人求职信范文
2013/12/07 职场文书
社区维稳工作方案
2014/06/06 职场文书
公安机关查摆剖析材料
2014/10/10 职场文书