github地址: https://github.com/cheesezh/python_design_patterns

题目

设计一个控制台程序, 模拟商场收银软件,根据客户购买商品的单价和数量,计算总价。

基础版本

  1. price = float(input("输入商品单价:"))
  2. number = int(input("输入商品数量:"))
  3. total = (price * number)
  4. print("当前总价: %.2f" % total)
  1. 输入商品单价:40
  2. 输入商品数量:9
  3. 当前总价: 360.00

点评

上述程序仅仅实现了基本功能,但是当商场有打折活动,例如八折,五折等,就不满足需求了,折扣的方法还可能有满减活动,例如满300减100,满500减200等。假设只有打折和满减两种促销活动,那么这就很像上一章节的计算器,支持正常收费,打折活动和满减活动三种计算方法,可以用简单工厂方法实现。

改进版本1.0——简单工厂模式

  1. from abc import ABCMeta, abstractmethod
  2. class CashBase():
  3. """
  4. 基础类
  5. """
  6. __metaclass__ = ABCMeta
  7. def __init__(self):
  8. self.final_price = None
  9. @abstractmethod
  10. def accept_cash(self):
  11. pass
  12. class CashNormal(CashBase):
  13. """
  14. 正常收费
  15. """
  16. def accept_cash(self, money):
  17. self.final_price = money
  18. return self.final_price
  19. class CashRebate(CashBase):
  20. """
  21. 打折活动
  22. """
  23. def __init__(self, rebate):
  24. self.rebate = rebate
  25. def accept_cash(self, money):
  26. self.final_price = money * self.rebate
  27. return self.final_price
  28. class CashReturn(CashBase):
  29. """
  30. 满减活动
  31. """
  32. def __init__(self, return_condition, return_money):
  33. self.return_condition = return_condition
  34. self.return_money = return_money
  35. def accept_cash(self, money):
  36. if money >= self.return_condition:
  37. self.final_price = money - self.return_money
  38. else:
  39. self.final_price = money
  40. return self.final_price
  41. class CashFactory():
  42. """
  43. 收费方式工厂类
  44. """
  45. # 类的变量,类似静态变量,通过`类名.变量名`访问
  46. cash_accepter_map = {
  47. "正常收费": CashNormal(),
  48. "满300减100": CashReturn(300, 100),
  49. "打8折": CashRebate(0.8)
  50. }
  51. @staticmethod
  52. def createCashAccepter(cash_type):
  53. if cash_type in CashFactory.cash_accepter_map:
  54. return CashFactory.cash_accepter_map[cash_type]
  55. else:
  56. return None

客户端代码

  1. price = float(input("输入商品单价:"))
  2. number = int(input("输入商品数量:"))
  3. cash_type_list = ["正常收费", "满300减100", "打8折"]
  4. for i in cash_type_list:
  5. print("{}:{}".format(cash_type_list.index(i)+1, i))
  6. cash_type_index = int(input("选择收费方式(1~3)"))
  7. total = price * number
  8. cash_accepter = CashFactory.createCashAccepter(cash_type_list[cash_type_index-1])
  9. print("应收: %.2f" % total)
  10. total = cash_accepter.accept_cash(total)
  11. print("实收: %.2f" % total)
  1. 输入商品单价:10
  2. 输入商品数量:50
  3. 1:正常收费
  4. 2:满300100
  5. 3:打8
  6. 选择收费方式(1~3)3
  7. 应收: 500.00
  8. 实收: 400.00

点评

  1. 如果同时支持打折和满减,需要如何处理?
  2. 简单工厂模式主要解决对象的创建问题,无法解决对象经常改动的问题,例如折扣和满减力度是经常变化的,不能每次改动都改代码;
  3. 算法经常改动, 需要用到策略模式;
  4. 封装变化点是面向对象的一种重要的思维方式。

策略模式

该模式定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户。

  1. from abc import ABCMeta, abstractmethod
  2. class CashBase():
  3. """
  4. 抽象策略:基础类
  5. """
  6. __metaclass__ = ABCMeta
  7. def __init__(self):
  8. self.final_price = None
  9. @abstractmethod
  10. def accept_cash(self):
  11. pass
  12. class CashNormal(CashBase):
  13. """
  14. 具体策略:正常收费
  15. """
  16. def accept_cash(self, money):
  17. self.final_price = money
  18. return self.final_price
  19. class CashRebate(CashBase):
  20. """
  21. 具体策略:打折活动
  22. """
  23. def __init__(self, rebate):
  24. self.rebate = rebate
  25. def accept_cash(self, money):
  26. self.final_price = money * self.rebate
  27. return self.final_price
  28. class CashReturn(CashBase):
  29. """
  30. 具体策略:满减活动
  31. """
  32. def __init__(self, return_condition, return_money):
  33. self.return_condition = return_condition
  34. self.return_money = return_money
  35. def accept_cash(self, money):
  36. if money >= self.return_condition:
  37. self.final_price = money - self.return_money
  38. else:
  39. self.final_price = money
  40. return self.final_price
  41. class CashContext():
  42. """
  43. 策略上下文类(基础版本),用具体策略类来配置,维护一个具体策略对象的引用
  44. """
  45. def __init__(self, cash_strategy):
  46. self.cash_strategy = cash_strategy
  47. def get_result(slef, money):
  48. return self.cash_strategy.accept_cash(money)

点评

在CashContext类中,我们需要传入一个具体策略类来进行配置,在商场收银软件这个场景中,那就是不同的收费策略,那么如何生成不同的收费策略对象呢?可以将策略模式和简单工厂相结合。

  1. class CashContext():
  2. """
  3. 策略上下文类(改进版本),用具体策略类来配置,维护一个具体策略对象的引用
  4. """
  5. # 类的变量,类似静态变量,通过`类名.变量名`访问
  6. cash_accepter_map = {
  7. "正常收费": CashNormal(),
  8. "满300减100": CashReturn(300, 100),
  9. "打8折": CashRebate(0.8)
  10. }
  11. def __init__(self, cash_type):
  12. self.cash_strategy = CashContext.cash_accepter_map[cash_type]
  13. def get_result(self, money):
  14. return self.cash_strategy.accept_cash(money)

客户端代码

  1. price = float(input("输入商品单价:"))
  2. number = int(input("输入商品数量:"))
  3. cash_type_list = ["正常收费", "满300减100", "打8折"]
  4. for i in cash_type_list:
  5. print("{}:{}".format(cash_type_list.index(i)+1, i))
  6. cash_type_index = int(input("选择收费方式(1~3)"))
  7. total = price * number
  8. cash_context = CashContext(cash_type_list[cash_type_index-1])
  9. print("应收: %.2f" % total)
  10. total = cash_context.get_result(total)
  11. print("实收: %.2f" % total)
  1. 输入商品单价:10
  2. 输入商品数量:10
  3. 1:正常收费
  4. 2:满300100
  5. 3:打8
  6. 选择收费方式(1~3)3
  7. 应收: 100.00
  8. 实收: 80.00

点评

策略模式+简单工厂和仅用简单工厂模式的区别在哪里呢?

  1. 简单工厂
  2. cash_accepter = CashFactory.createCashAccepter(cash_type_list[cash_type_index-1])
  3. ...
  4. total = cash_accepter.accept_cash(total)
  5. 策略模式+简单工厂
  6. cash_context = CashContext(cash_type_list[cash_type_index-1])
  7. ...
  8. total = cash_context.get_result(total)
  • 简单工厂需要让客户端认识两个类,CashFactoryCashBase
  • 策略模式+简单工厂,客户端只需要认识一个类,CashContext
  • 客户端实例化的是CashContext的对象,调用的是CashContextget_result方法,这使得具体的收费策略彻底与客户端分离,甚至连策略的基类CashBase都不需要客户端认识。

策略模式解析

  1. 策略模式是一种定义一系列算法的方法,从概念上来看,所有这些算法完成的都是相同的工作,只是实现不同,它可以以相同的方式调用素有的算法,减少了各种算法类与使用算法类之间的耦合[DPE]。
  2. 策略模式的Strategy层次为Context定义了一系列的可供重用的算法或行为。继承有助于析取出这些算法中的公共功能[DP],例如计算费用的结果get_result。
  3. 策略模式可以简化单元测试,因为每个算法都有自己的类,可以用过自己的接口单独测试[DPE]。
  4. 策略模式是用来封装算法的,但是实践中,可以用它来封装几乎任何类型的规则,只要需要不同时间应用不同业务规则,就可以考虑使用策略模式处理这种变化的可能性[DPE]。

美中不足

在CashContext中用到了一个dict()型的类的变量cash_accepter_map保存各种算法策略,如果新增满200减50的策略,那么还要更新cash_accepter_map,这显得并不优雅,任何需要的变更都需要成本,但是成本的高低是有差异的,为了更加优雅,降低变更成本,可以使用反射技术,这一技术将在抽象工厂模式中介绍。

[Python设计模式] 第2章 商场收银软件——策略模式的更多相关文章

  1. [Python设计模式] 第25章 联合国维护世界和平——中介者模式

    github地址:https://github.com/cheesezh/python_design_patterns 题目背景 联合国在世界上就是中介者的角色,各国之间的关系复杂,类似不同的对象和对 ...

  2. [Python设计模式] 第23章 烤串的哲学——命令模式

    github地址:https://github.com/cheesezh/python_design_patterns 题目1 用程序模拟,顾客直接向烤串师傅提需求. class Barbecuer( ...

  3. [Python设计模式] 第12章 基金理财更省事——外观模式

    github地址:https://github.com/cheesezh/python_design_patterns 题目1 用程序模拟股民直接炒股的代码,比如股民投资了股票1,股票2,股票3,国债 ...

  4. [Python设计模式] 第10章 怎么出试卷?——模版方法模式

    github地址:https://github.com/cheesezh/python_design_patterns 题目 小时候数学老师的随堂测验,都是老师在黑板上写题目,学生在下边抄,然后再做题 ...

  5. 读《大话设计模式》——应用工厂模式的"商场收银系统"(WinForm)

    要做的是一个商场收银软件,营业员根据客户购买商品单价和数量,向客户收费.两个文本框,输入单价和数量,再用个列表框来记录商品的合计,最终用一个按钮来算出总额就可以了,还需要一个重置按钮来重新开始. 核心 ...

  6. php 商场收银收费系统,使用的策略模式

    <?php//策略模式就是你有很多的方法,选择一种适合自己的,// 单例模式就是只有一个实例对象,不需要每个文件都要加载,比如连接数据库,// 工厂模式就是 //策略模式 优惠系统.工资计算系统 ...

  7. 思迅/泰格/科脉/收银软件/商超软件数据库修复解决断电造成损坏的mdb\dat文件SQL数据库 置疑 修复 恢复

    拥有专业管理软件数据库修复技术工程师,专业提供管家婆.美萍.思迅.科脉等管理软件技术服务,电脑维修\重装系统技 术服务.无法登陆打不开等出错问题处理(连接失败,请输入正确的服务器名,SQL Serve ...

  8. javascript 写策略模式,商场收银打折优惠策略

    [Decode error - output not utf-8] ----------------------------- 购物清单 方便面 : 100 x 50 = 5000 | 4000 菊花 ...

  9. 《Head First 设计模式》例子的C++实现(1 策略模式)

    最近在学习设计模式,用的是 <Head First 设计模式>这本书.感觉这本书写的还是很不错的,深入浅出的介绍了各种常用的设计模式.唯一有点不方便的地方是这本书的例子全都是用的 Java ...

随机推荐

  1. list的遍历

    package list; import java.util.ArrayList;import java.util.Iterator;import java.util.List; /* * list的 ...

  2. <转>用 Java 技术创建 RESTful Web 服务

    转自:https://www.ibm.com/developerworks/cn/web/wa-jaxrs/#N1017E JAX-RS:一种更为简单.可移植性更好的替代方式 Dustin Amrhe ...

  3. poj 1251 poj 1258 hdu 1863 poj 1287 poj 2421 hdu 1233 最小生成树模板题

    poj 1251  && hdu 1301 Sample Input 9 //n 结点数A 2 B 12 I 25B 3 C 10 H 40 I 8C 2 D 18 G 55D 1 E ...

  4. python学习之for循环

    Python for循环可以遍历任何序列的项目,如一个列表或者一个字符串. 实例: #!/usr/bin/env python for letter in 'Python': # 第一个实例 prin ...

  5. Codeforces Round #395 (Div. 2)

    今天自己模拟了一套题,只写出两道来,第三道时间到了过了几分钟才写出来,啊,太菜了. A. Taymyr is calling you 水题,问你在z范围内  两个序列  n,2*n,3*n...... ...

  6. Codeforces 757D - Felicity's Big Secret Revealed

    757D - Felicity's Big Secret Revealed 题目大意:给你一串有n(n<=75)个0或1组成的串,让你划最多n+1条分割线,第一条分割线的前面和最后一条分割线的后 ...

  7. Cpu 主频与睿频

    主频就是一颗CPU的运行频率.比如一颗CPU是2.3G,无论是单核还是多核,所有的核心都是工作在2.3G. 睿频是Intel的一项加速技术,指当启动一个运行程序后,处理器会自动加速到合适的频率,而原来 ...

  8. js类型判断-丰富加好用

    一, 自己有时候写一些东西,要做类型判断,还有测试的时候,对于原生的和jQuery中的类型判断,实在不敢恭维,所以就写了一个好用的类型判断,一般情况都够用的. function test(type) ...

  9. LoRaWAN 1.1 网络协议规范 - 2 LoRaWAN选项介绍

    LoRaWAN 1.1 网络协议规范 LoRaWAN 1.1 版本封稿很久了也没有完整啃过一遍,最近边啃边翻译,趁着这个机会把它码下来. 如果觉得哪里有问题,欢迎留言斧正. 翻译不易,转载请申明出处和 ...

  10. 从新安装SQLserver 过程中报错问题合集

    1.安装SQL SERVER2008 到安装支持文件就闪退? 分析:这个是由于安装目录没有删除干净导致的,我遗漏了一个文件夹:microsoft Management console文件夹没有删除的原 ...