详解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运算符重载用法实例
May 28 Python
Python制作爬虫抓取美女图
Jan 20 Python
windows下python连接oracle数据库
Jun 07 Python
用十张图详解TensorFlow数据读取机制(附代码)
Feb 06 Python
使用python爬取B站千万级数据
Jun 08 Python
Python pandas.DataFrame 找出有空值的行
Sep 09 Python
浅析python中while循环和for循环
Nov 19 Python
Python操作多维数组输出和矩阵运算示例
Nov 28 Python
python 实现矩阵填充0的例子
Nov 29 Python
python实现百度OCR图片识别过程解析
Jan 17 Python
Python如何读写字节数据
Aug 05 Python
Python常用base64 md5 aes des crc32加密解密方法汇总
Nov 06 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下图片文字混合水印与缩略图实现代码
2009/12/11 PHP
PHP基础学习之流程控制的实现分析
2013/04/28 PHP
5种PHP创建数组的实例代码分享
2014/01/17 PHP
经典PHP加密解密函数Authcode()修复版代码
2015/04/05 PHP
PHP基于DOM创建xml文档的方法示例
2017/02/08 PHP
php中序列化与反序列化详解
2017/02/13 PHP
Laravel 实现关系模型取出需要的字段
2019/10/10 PHP
PHP filter_var() 函数, 验证判断EMAIL,URL等
2021/03/09 PHP
简单通用的JS滑动门代码
2008/12/19 Javascript
Javascript 解疑
2009/11/11 Javascript
jQuery中fadeIn、fadeOut、fadeTo的使用方法(图片显示与隐藏)
2013/05/08 Javascript
wap图片滚动特效无css3元素纯js脚本编写
2014/08/22 Javascript
BAT及各大互联网公司2014前端笔试面试题--JavaScript篇
2014/10/29 Javascript
原生JS实现-星级评分系统的简单实例
2016/08/21 Javascript
requirejs按需加载angularjs文件实例
2017/06/08 Javascript
nodejs微信扫码支付功能实现
2018/02/17 NodeJs
JS封装的模仿qq右下角消息弹窗功能示例
2018/08/22 Javascript
Vue.js点击切换按钮改变内容的实例讲解
2018/08/22 Javascript
vue表单自定义校验规则介绍
2018/08/28 Javascript
小程序文字跑马灯效果
2018/12/28 Javascript
JavaScript中filter的用法实例分析
2019/02/27 Javascript
ES6 Object属性新的写法实例小结
2019/06/25 Javascript
angular组件间通讯的实现方法示例
2020/05/07 Javascript
python进阶教程之函数对象(函数也是对象)
2014/08/30 Python
Python实现将绝对URL替换成相对URL的方法
2015/06/28 Python
Python如何通过subprocess调用adb命令详解
2017/08/27 Python
Python读取mat文件,并转为csv文件的实例
2018/07/04 Python
去除python中的字符串空格的简单方法
2020/12/22 Python
银行见习期自我鉴定
2014/01/29 职场文书
创业计划书的主要内容有哪些
2014/01/29 职场文书
秋季开学典礼主持词
2014/03/19 职场文书
超越自我演讲稿
2014/05/21 职场文书
党的群众路线剖析材料
2014/10/09 职场文书
英文慰问信
2015/02/14 职场文书
员工规章制度范本
2015/08/07 职场文书
为什么中国式养孩子很累?
2019/08/07 职场文书