详解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实现从URL地址提取文件名的方法
May 15 Python
wxpython中自定义事件的实现与使用方法分析
Jul 21 Python
Python爬虫设置代理IP(图文)
Dec 23 Python
提升Python程序性能的7个习惯
Apr 14 Python
python中时间、日期、时间戳的转换的实现方法
Jul 06 Python
对python3中的RE(正则表达式)-详细总结
Jul 23 Python
python基础 range的用法解析
Aug 23 Python
Python 可变类型和不可变类型及引用过程解析
Sep 27 Python
使用python制作一个解压缩软件
Nov 13 Python
python实现图书馆抢座(自动预约)功能的示例代码
Sep 29 Python
五分钟学会怎么用python做一个简单的贪吃蛇
Jan 12 Python
Python爬虫自动化爬取b站实时弹幕实例方法
Jan 26 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压缩多个CSS为一个css的代码并缓存
2011/04/21 PHP
PHP实现AES256加密算法实例
2014/09/22 PHP
PHP编写文件多服务器同步程序
2016/07/02 PHP
CI框架封装的常用图像处理方法(缩略图,水印,旋转,上传等)
2016/11/22 PHP
PHP树形结构tree类用法示例
2019/02/01 PHP
基于PHP实现短信验证码发送次数限制
2020/07/11 PHP
一款JavaScript压缩工具:X2JSCompactor
2007/06/13 Javascript
ExtJS[Desktop]实现图标换行示例代码
2013/11/17 Javascript
浅谈js中变量初始化
2015/02/03 Javascript
JavaScript ES5标准中新增的Array方法
2016/06/28 Javascript
JavaScript中最容易混淆的作用域、提升、闭包知识详解(推荐)
2016/09/05 Javascript
Bootstrap Search Suggest使用例子
2016/12/21 Javascript
微信小程序-小说阅读小程序实例(demo)
2017/01/12 Javascript
node.js中EJS 模板快速入门教程
2017/05/08 Javascript
jQuery制作input提示内容(兼容IE8以上)
2017/07/05 jQuery
vuex提交state&amp;&amp;实时监听state数据的改变方法
2018/09/16 Javascript
Node.js + express基本用法教程
2019/03/14 Javascript
vue监听键盘事件的相关总结
2021/01/29 Vue.js
[00:56]跨越时空加入战场 全新祈求者身心“失落奇艺侍祭”展示
2019/07/20 DOTA
Linux下通过python访问MySQL、Oracle、SQL Server数据库的方法
2016/04/23 Python
Python selenium如何设置等待时间
2016/09/15 Python
Django添加favicon.ico图标的示例代码
2018/08/07 Python
Python 3.6 -win64环境安装PIL模块的教程
2019/06/20 Python
Python操作SQLite/MySQL/LMDB数据库的方法
2019/11/07 Python
使用python turtle画高达
2020/01/19 Python
python中matplotlib实现随鼠标滑动自动标注代码
2020/04/23 Python
详解Pycharm与anaconda安装配置指南
2020/08/25 Python
html5使用canvas绘制文字特效
2014/12/15 HTML / CSS
求职简历的自我评价
2014/01/31 职场文书
大学毕业自我鉴定范文
2014/02/03 职场文书
《宿建德江》教学反思
2014/04/23 职场文书
乡村教师党员四风问题对照检查材料思想汇报
2014/10/08 职场文书
2014年社区宣传工作总结
2014/12/02 职场文书
2015年教学副校长工作总结
2015/07/22 职场文书
2016年小学“我们的节日·中秋节”活动总结
2016/04/05 职场文书
创业计划书之个人工作室
2019/08/22 职场文书