Python设计模式: 最佳的"策略"模式实践代码

今天抽空看了下流畅的python,发现里面介绍了不少python自带的库的使用实例,用起来非常的优雅。

平时用Python来写爬虫比较多,所以最近一直在看设计模式的内容。刚好这本书里面有一章单独讲设计模式的,讲的还不错,特意摘录出来。

这段代码的需求背景是电商平台常用的促销策略:

  1. 用户的积分超过1000时,订单总价优惠5%的金额
  2. 购买商品的种类超过10种时,订单总价优惠7%的金额
  3. 单个商品购买数量超过20件时,该商品获得10%的折扣

具体代码如下:

  1. from collections import namedtuple
  2. Customer = namedtuple('Customer', 'name fidelity')
  3. class LineItem:
  4. def __init__(self, product, quantity, price):
  5. self.product = product
  6. self.quantity = quantity
  7. self.price = price
  8. def total(self):
  9. return self.price * self.quantity
  10. class Order: # the Context
  11. def __init__(self, customer, cart, promotion=None):
  12. self.customer = customer
  13. self.cart = list(cart)
  14. self.promotion = promotion
  15. def total(self):
  16. if not hasattr(self, '__total'):
  17. self.__total = sum(item.total() for item in self.cart)
  18. return self.__total
  19. def due(self):
  20. if self.promotion is None:
  21. discount = 0
  22. else:
  23. discount = self.promotion(self)
  24. return self.total() - discount
  25. def __repr__(self):
  26. fmt = '<Order total: {:.2f} due: {:.2f}>'
  27. return fmt.format(self.total(), self.due())
  28. promos = []
  29. def promotion(promo_func):
  30. promos.append(promo_func)
  31. return promo_func
  32. @promotion
  33. def fidelity_promo(order): # <3>
  34. """5% discount for customers with 1000 or more fidelity points"""
  35. return order.total() * .05 if order.customer.fidelity >= 1000 else 0
  36. @promotion
  37. def bulk_item_promo(order):
  38. """10% discount for each LineItem with 20 or more units"""
  39. discount = 0
  40. for item in order.cart:
  41. if item.quantity >= 20:
  42. discount += item.total() * .1
  43. return discount
  44. @promotion
  45. def large_order_promo(order):
  46. """7% discount for orders with 10 or more distinct items"""
  47. distinct_items = {item.product for item in order.cart}
  48. if len(distinct_items) >= 10:
  49. return order.total() * .07
  50. return 0
  51. def best_promo(order):
  52. return max(promo(order) for promo in promos)
  53. # # BEGIN STRATEGY_TESTS
  54. #
  55. # >>> joe = Customer('John Doe', 0) # <1>
  56. # >>> ann = Customer('Ann Smith', 1100)
  57. # >>> cart = [LineItem('banana', 4, .5),
  58. # ... LineItem('apple', 10, 1.5),
  59. # ... LineItem('watermellon', 5, 5.0)]
  60. # >>> Order(joe, cart, fidelity_promo) # <2>
  61. # <Order total: 42.00 due: 42.00>
  62. # >>> Order(ann, cart, fidelity_promo)
  63. # <Order total: 42.00 due: 39.90>
  64. # >>> banana_cart = [LineItem('banana', 30, .5),
  65. # ... LineItem('apple', 10, 1.5)]
  66. # >>> Order(joe, banana_cart, bulk_item_promo) # <3>
  67. # <Order total: 30.00 due: 28.50>
  68. # >>> long_order = [LineItem(str(item_code), 1, 1.0)
  69. # ... for item_code in range(10)]
  70. # >>> Order(joe, long_order, large_order_promo)
  71. # <Order total: 10.00 due: 9.30>
  72. # >>> Order(joe, cart, large_order_promo)
  73. # <Order total: 42.00 due: 42.00>
  74. # best promo========================================
  75. # >>> Order(joe, long_order, best_promo)
  76. # <Order total: 10.00 due: 9.30>
  77. # >>> Order(joe, banana_cart, best_promo)
  78. # <Order total: 30.00 due: 28.50>
  79. # >>> Order(ann, cart, best_promo)
  80. # <Order total: 42.00 due: 39.90>
  81. # # END STRATEGY_TESTS
  82. # """
  83. # BEGIN STRATEGY

这段代码得益于装饰器的使用,实现起来非常优雅,而且逻辑清晰、代码简洁,仅用了77行代码,就实现了策略模式。

但是,这个代码是经过了3次迭代、优化之后的。

第一版:使用ABC抽象基类来实现

在第一个版本中,用了ABC抽象基类来实现,代码非常的啰嗦,感觉像在写JAVA

  1. # 这里只贴核心逻辑代码
  2. from abc import ABC, abstractmethod
  3. class Promotion(ABC): # 策略:抽象基类
  4. @abstractmethod
  5. def discount(self, order):
  6. """Return discount as a positive dollar amount"""
  7. class FidelityPromo(Promotion): # 第一个策略
  8. """5% discount for customers with 1000 or more fidelity points
  9. 拥有1000或更高积分的客户可享受5%的折扣
  10. """
  11. def discount(self, order):
  12. return order.total() * .05 if order.customer.fidelity >= 1000 else 0
  13. class BulkItemPromo(Promotion): # 第二个策略
  14. """10% discount for each LineItem with 20 or more units
  15. 单件商品购买超过20个,商品总价获得10%折扣"""
  16. def discount(self, order):
  17. discount = 0
  18. for item in order.cart:
  19. if item.quantity >= 20:
  20. discount += item.total() * .1
  21. return discount
  22. class LargeOrderPromo(Promotion): # 第三个策略
  23. """7% discount for orders with 10 or more distinct items
  24. 购买商品的各类超过10个,获得7%的折扣
  25. """
  26. def discount(self, order):
  27. distinct_items = {item.product for item in order.cart}
  28. if len(distinct_items) >= 10:
  29. return order.total() * .07
  30. return 0

第二版:使用函数实现,更加扁平化,可复用

  1. def fidelity_promo(order):
  2. """5% discount for customers with 1000 or more fidelity points"""
  3. return order.total() * .05 if order.customer.fidelity >= 1000 else 0
  4. def bulk_item_promo(order):
  5. """10% discount for each LineItem with 20 or more units"""
  6. discount = 0
  7. for item in order.cart:
  8. if item.quantity >= 20:
  9. discount += item.total() * .1
  10. return discount
  11. def large_order_promo(order):
  12. """7% discount for orders with 10 or more distinct items"""
  13. distinct_items = {item.product for item in order.cart}
  14. if len(distinct_items) >= 10:
  15. return order.total() * .07
  16. return 0
  17. # BEGIN STRATEGY_BEST
  18. promos = [fidelity_promo, bulk_item_promo, large_order_promo] # <1>
  19. def best_promo(order): # <2>
  20. """Select best discount available
  21. """
  22. return max(promo(order) for promo in promos) # <3>

好一些,主要的变动是抽象类撤掉了,改成函数来使用,实现更加的扁平化。而且不用实例化那么多对象。

第三版:把促销逻辑封装到一个模块当中

  1. import inspect
  2. import promotions
  3. # 把上面那3个促销策略代码挪到promotions模块中
  4. # 这个模块里只有这3个函数
  5. promos = [func for name, func in inspect.getmembers(promotions, inspect.isfunction)]
  6. def best_promo(order):
  7. """Select best discount available
  8. """
  9. return max(promo(order) for promo in promos)

其实第三版已经很优雅了,所有的促销逻辑全部都封装到promotions中,以后要增加新的促销策略,只需要去改promotions文件里面的代码就好了。

参考资料:

  1. Sample code for Chapter 6 - "Design patterns with first class functions"

Python设计模式: 最佳的"策略"模式实践代码的更多相关文章

  1. JavaScript设计模式 Item 7 --策略模式Strategy

    1.策略模式的定义 何为策略?比如我们要去某个地方旅游,可以根据具体的实际情况来选择出行的线路. 如果没有时间但是不在乎钱,可以选择坐飞机. 如果没有钱,可以选择坐大巴或者火车. 如果再穷一点,可以选 ...

  2. 简介Python设计模式中的代理模式与模板方法模式编程

    简介Python设计模式中的代理模式与模板方法模式编程 这篇文章主要介绍了Python设计模式中的代理模式与模板方法模式编程,文中举了两个简单的代码片段来说明,需要的朋友可以参考下 代理模式 Prox ...

  3. 【转】设计模式 ( 十八 ) 策略模式Strategy(对象行为型)

    设计模式 ( 十八 ) 策略模式Strategy(对象行为型) 1.概述 在软件开发中也常常遇到类似的情况,实现某一个功能有多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成 ...

  4. 设计模式 ( 十八 ) 策略模式Strategy(对象行为型)

    设计模式 ( 十八 ) 策略模式Strategy(对象行为型) 1.概述 在软件开发中也经常遇到类似的情况,实现某一个功能有多种算法或者策略,我们能够依据环境或者条件的不同选择不同的算法或者策略来完毕 ...

  5. 《Head First 设计模式》[01] 策略模式

    <Head First 设计模式>(点击查看详情) 1.写在前面的话 之前在列书单的时候,看网友对于设计模式的推荐里说,设计模式的书类别都大同小异,于是自己就选择了Head First系列 ...

  6. python设计模式之常用创建模式总结(二)

    前言 设计模式的创建模式终极目标是如何使用最少量最少需要修改的代码,传递最少的参数,消耗系统最少的资源创建可用的类的实例对象. 系列文章 python设计模式之单例模式(一) python设计模式之常 ...

  7. javascript设计模式--策略模式

    javascript策略模式总结 1.什么是策略模式? 策略模式的定义是:定义一系列的算法,把他们独立封装起来,并且可以相互替换. 例如我们需要写一段代码来计算员工的奖金.当绩效为a时,奖金为工资的5 ...

  8. python设计模式之责任链模式

    python设计模式之责任链模式 开发一个应用时,多数时候我们都能预先知道哪个方法能处理某个特定请求.然而,情况并非总是如此.例如,想想任意一种广播计算机网络,例如最早的以太网实现.在广播计算机网络中 ...

  9. python设计模式之享元模式

    python设计模式之享元模式 由于对象创建的开销,面向对象的系统可能会面临性能问题.性能问题通常在资源受限的嵌入式系统中出现,比如智能手机和平板电脑.大型复杂系统中也可能会出现同样的问题,因为要在其 ...

随机推荐

  1. js笔记22

    1.在拖拽元素的时候,如果元素的内部加了文字或者图片,拖拽效果会失灵? 浏览器会给文字和图片一个默认行为,当文字和图片被选中的时候,会有一个拖拽的效果,即使我们没有人为给他添加.所以当我们点击这个元素 ...

  2. CVPR2021 | 开放世界的目标检测

    ​ 本文将介绍一篇很有意思的论文,该方向比较新,故本文保留了较多论文中的设计思路,背景知识等相关内容. 前言: 人类具有识别环境中未知对象实例的本能.当相应的知识最终可用时,对这些未知实例的内在好奇心 ...

  3. Sai学习笔记

    颜色模块的功能介绍 色轮 RGB滑块 HSV滑块(常用) H:色相 S:纯度 V:明度 中间色条,主要用来混色 颜料盒 调色板 选择工具的使用 选择框 快捷键:Ctrl+D 套索 魔棒 图文工具使用 ...

  4. 7、基本数据类型(tuple)

    7.1.tuple类: 1.元组元素用小括号括起来,用逗号分割每个元素,一般写元组的时候,推荐在最后加入逗号,该 逗号不占元素位置,目的是为了方便识别: tu = (111, "alex&q ...

  5. RabbitMQ重试机制

    消费端在处理消息过程中可能会报错,此时该如何重新处理消息呢?解决方案有以下两种. 在redis或者数据库中记录重试次数,达到最大重试次数以后消息进入死信队列或者其他队列,再单独针对这些消息进行处理: ...

  6. 10 shell test命令

    0.test命令的用法 1.与数值比较相关的test选项 2.与字符串判断相关的 test 选项 3.与文件检测相关的test选项 4.与逻辑运算相关的test选项 5.注意点与总结 1.test中变 ...

  7. Exception 和Error异常大部分人都犯过的错。

    先看再点赞,给自己一点思考的时间,如果对自己有帮助,微信搜索[程序职场]关注这个执着的职场程序员. 我有什么:职场规划指导,技能提升方法,讲不完的职场故事,个人成长经验. 1,简介 Exception ...

  8. 「NOIP2017」宝藏

    「NOIP2017」宝藏 题解 博客阅读效果更佳 又到了一年一度NOIPCSP-S 赛前复习做真题的时间 于是就遇上了这道题 首先观察数据范围 \(1 \le n \le 12\) ,那么极大可能性是 ...

  9. python02篇 字典、元组、切片

    一.字典 1.1 字典的常用方法 # 字典 数据类型 {} key-value # list是挨个循环查找,字典是根据key查找value,比list遍历效率高 d = { 'username': ' ...

  10. Gitbook配置目录折叠

    如果有多个目录,Gitbook在浏览器上打开时,默认所有的目录都会打开,当目录比较多时,全部显示不利于阅读. 可以使用插件配置目录折叠,使得打开浏览器时这些目录默认是关闭的. 在执行gitbook i ...