[Python设计模式] 第2章 商场收银软件——策略模式
github地址: https://github.com/cheesezh/python_design_patterns
题目
设计一个控制台程序, 模拟商场收银软件,根据客户购买商品的单价和数量,计算总价。
基础版本
price = float(input("输入商品单价:"))
number = int(input("输入商品数量:"))
total = (price * number)
print("当前总价: %.2f" % total)
输入商品单价:40
输入商品数量:9
当前总价: 360.00
点评
上述程序仅仅实现了基本功能,但是当商场有打折活动,例如八折,五折等,就不满足需求了,折扣的方法还可能有满减活动,例如满300减100,满500减200等。假设只有打折和满减两种促销活动,那么这就很像上一章节的计算器,支持正常收费,打折活动和满减活动三种计算方法,可以用简单工厂方法实现。
改进版本1.0——简单工厂模式
from abc import ABCMeta, abstractmethod
class CashBase():
"""
基础类
"""
__metaclass__ = ABCMeta
def __init__(self):
self.final_price = None
@abstractmethod
def accept_cash(self):
pass
class CashNormal(CashBase):
"""
正常收费
"""
def accept_cash(self, money):
self.final_price = money
return self.final_price
class CashRebate(CashBase):
"""
打折活动
"""
def __init__(self, rebate):
self.rebate = rebate
def accept_cash(self, money):
self.final_price = money * self.rebate
return self.final_price
class CashReturn(CashBase):
"""
满减活动
"""
def __init__(self, return_condition, return_money):
self.return_condition = return_condition
self.return_money = return_money
def accept_cash(self, money):
if money >= self.return_condition:
self.final_price = money - self.return_money
else:
self.final_price = money
return self.final_price
class CashFactory():
"""
收费方式工厂类
"""
# 类的变量,类似静态变量,通过`类名.变量名`访问
cash_accepter_map = {
"正常收费": CashNormal(),
"满300减100": CashReturn(300, 100),
"打8折": CashRebate(0.8)
}
@staticmethod
def createCashAccepter(cash_type):
if cash_type in CashFactory.cash_accepter_map:
return CashFactory.cash_accepter_map[cash_type]
else:
return None
客户端代码
price = float(input("输入商品单价:"))
number = int(input("输入商品数量:"))
cash_type_list = ["正常收费", "满300减100", "打8折"]
for i in cash_type_list:
print("{}:{}".format(cash_type_list.index(i)+1, i))
cash_type_index = int(input("选择收费方式(1~3)"))
total = price * number
cash_accepter = CashFactory.createCashAccepter(cash_type_list[cash_type_index-1])
print("应收: %.2f" % total)
total = cash_accepter.accept_cash(total)
print("实收: %.2f" % total)
输入商品单价:10
输入商品数量:50
1:正常收费
2:满300减100
3:打8折
选择收费方式(1~3)3
应收: 500.00
实收: 400.00
点评
- 如果同时支持打折和满减,需要如何处理?
- 简单工厂模式主要解决对象的创建问题,无法解决对象经常改动的问题,例如折扣和满减力度是经常变化的,不能每次改动都改代码;
- 算法经常改动, 需要用到策略模式;
- 封装变化点是面向对象的一种重要的思维方式。
策略模式
该模式定义了算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户。
from abc import ABCMeta, abstractmethod
class CashBase():
"""
抽象策略:基础类
"""
__metaclass__ = ABCMeta
def __init__(self):
self.final_price = None
@abstractmethod
def accept_cash(self):
pass
class CashNormal(CashBase):
"""
具体策略:正常收费
"""
def accept_cash(self, money):
self.final_price = money
return self.final_price
class CashRebate(CashBase):
"""
具体策略:打折活动
"""
def __init__(self, rebate):
self.rebate = rebate
def accept_cash(self, money):
self.final_price = money * self.rebate
return self.final_price
class CashReturn(CashBase):
"""
具体策略:满减活动
"""
def __init__(self, return_condition, return_money):
self.return_condition = return_condition
self.return_money = return_money
def accept_cash(self, money):
if money >= self.return_condition:
self.final_price = money - self.return_money
else:
self.final_price = money
return self.final_price
class CashContext():
"""
策略上下文类(基础版本),用具体策略类来配置,维护一个具体策略对象的引用
"""
def __init__(self, cash_strategy):
self.cash_strategy = cash_strategy
def get_result(slef, money):
return self.cash_strategy.accept_cash(money)
点评
在CashContext类中,我们需要传入一个具体策略类来进行配置,在商场收银软件这个场景中,那就是不同的收费策略,那么如何生成不同的收费策略对象呢?可以将策略模式和简单工厂相结合。
class CashContext():
"""
策略上下文类(改进版本),用具体策略类来配置,维护一个具体策略对象的引用
"""
# 类的变量,类似静态变量,通过`类名.变量名`访问
cash_accepter_map = {
"正常收费": CashNormal(),
"满300减100": CashReturn(300, 100),
"打8折": CashRebate(0.8)
}
def __init__(self, cash_type):
self.cash_strategy = CashContext.cash_accepter_map[cash_type]
def get_result(self, money):
return self.cash_strategy.accept_cash(money)
客户端代码
price = float(input("输入商品单价:"))
number = int(input("输入商品数量:"))
cash_type_list = ["正常收费", "满300减100", "打8折"]
for i in cash_type_list:
print("{}:{}".format(cash_type_list.index(i)+1, i))
cash_type_index = int(input("选择收费方式(1~3)"))
total = price * number
cash_context = CashContext(cash_type_list[cash_type_index-1])
print("应收: %.2f" % total)
total = cash_context.get_result(total)
print("实收: %.2f" % total)
输入商品单价:10
输入商品数量:10
1:正常收费
2:满300减100
3:打8折
选择收费方式(1~3)3
应收: 100.00
实收: 80.00
点评
策略模式+简单工厂和仅用简单工厂模式的区别在哪里呢?
简单工厂
cash_accepter = CashFactory.createCashAccepter(cash_type_list[cash_type_index-1])
...
total = cash_accepter.accept_cash(total)
策略模式+简单工厂
cash_context = CashContext(cash_type_list[cash_type_index-1])
...
total = cash_context.get_result(total)
- 简单工厂需要让客户端认识两个类,
CashFactory和CashBase - 策略模式+简单工厂,客户端只需要认识一个类,
CashContext - 客户端实例化的是
CashContext的对象,调用的是CashContext的get_result方法,这使得具体的收费策略彻底与客户端分离,甚至连策略的基类CashBase都不需要客户端认识。
策略模式解析
- 策略模式是一种定义一系列算法的方法,从概念上来看,所有这些算法完成的都是相同的工作,只是实现不同,它可以以相同的方式调用素有的算法,减少了各种算法类与使用算法类之间的耦合[DPE]。
- 策略模式的Strategy层次为Context定义了一系列的可供重用的算法或行为。继承有助于析取出这些算法中的公共功能[DP],例如计算费用的结果get_result。
- 策略模式可以简化单元测试,因为每个算法都有自己的类,可以用过自己的接口单独测试[DPE]。
- 策略模式是用来封装算法的,但是实践中,可以用它来封装几乎任何类型的规则,只要需要不同时间应用不同业务规则,就可以考虑使用策略模式处理这种变化的可能性[DPE]。
美中不足
在CashContext中用到了一个dict()型的类的变量cash_accepter_map保存各种算法策略,如果新增满200减50的策略,那么还要更新cash_accepter_map,这显得并不优雅,任何需要的变更都需要成本,但是成本的高低是有差异的,为了更加优雅,降低变更成本,可以使用反射技术,这一技术将在抽象工厂模式中介绍。
[Python设计模式] 第2章 商场收银软件——策略模式的更多相关文章
- [Python设计模式] 第25章 联合国维护世界和平——中介者模式
github地址:https://github.com/cheesezh/python_design_patterns 题目背景 联合国在世界上就是中介者的角色,各国之间的关系复杂,类似不同的对象和对 ...
- [Python设计模式] 第23章 烤串的哲学——命令模式
github地址:https://github.com/cheesezh/python_design_patterns 题目1 用程序模拟,顾客直接向烤串师傅提需求. class Barbecuer( ...
- [Python设计模式] 第12章 基金理财更省事——外观模式
github地址:https://github.com/cheesezh/python_design_patterns 题目1 用程序模拟股民直接炒股的代码,比如股民投资了股票1,股票2,股票3,国债 ...
- [Python设计模式] 第10章 怎么出试卷?——模版方法模式
github地址:https://github.com/cheesezh/python_design_patterns 题目 小时候数学老师的随堂测验,都是老师在黑板上写题目,学生在下边抄,然后再做题 ...
- 读《大话设计模式》——应用工厂模式的"商场收银系统"(WinForm)
要做的是一个商场收银软件,营业员根据客户购买商品单价和数量,向客户收费.两个文本框,输入单价和数量,再用个列表框来记录商品的合计,最终用一个按钮来算出总额就可以了,还需要一个重置按钮来重新开始. 核心 ...
- php 商场收银收费系统,使用的策略模式
<?php//策略模式就是你有很多的方法,选择一种适合自己的,// 单例模式就是只有一个实例对象,不需要每个文件都要加载,比如连接数据库,// 工厂模式就是 //策略模式 优惠系统.工资计算系统 ...
- 思迅/泰格/科脉/收银软件/商超软件数据库修复解决断电造成损坏的mdb\dat文件SQL数据库 置疑 修复 恢复
拥有专业管理软件数据库修复技术工程师,专业提供管家婆.美萍.思迅.科脉等管理软件技术服务,电脑维修\重装系统技 术服务.无法登陆打不开等出错问题处理(连接失败,请输入正确的服务器名,SQL Serve ...
- javascript 写策略模式,商场收银打折优惠策略
[Decode error - output not utf-8] ----------------------------- 购物清单 方便面 : 100 x 50 = 5000 | 4000 菊花 ...
- 《Head First 设计模式》例子的C++实现(1 策略模式)
最近在学习设计模式,用的是 <Head First 设计模式>这本书.感觉这本书写的还是很不错的,深入浅出的介绍了各种常用的设计模式.唯一有点不方便的地方是这本书的例子全都是用的 Java ...
随机推荐
- zabbix server+agent+proxy搭建性能监控平台
这是新找到了配置文件配置方法但未尝试 每个模块工作职责: Zabbix Server:负责接收agent发送的报告信息的核心组件,所有配置,统计数据及操作数据均由其组织进行: Database Sto ...
- 【C++】三大概念要分清--重载,隐藏(重定义,覆盖(重写)
{ c++三大概念要分清--重载,隐藏(重定义),覆盖(重写)} 重载 • 概念:在同一个作用域内:函数名相同,参数列表不同(参数个数不同,或者参数类型不同,或者参数个数和参数类型都不同),返回值类 ...
- vtiger7菜单管理
添加了新模块,但是菜单上却没显示. 和菜单相关的表有4张 我们要把新建的message放到support模块下面 1.把默认的父级目录tools改成support 2. app2tab 0表示不显示, ...
- python_cookbook之路:数据结构-解压可迭代对象赋值给多个变量以及扩展的迭代解压语法(*)
1.一一对应: >>> data = [ 'ACME', 50, 91.1, (2012, 12, 21) ] >>> name, shares, price, d ...
- Matrix PKU 2155
问题描述 给定N * N矩阵A,其元素为0或1.A [i,j]表示第i行和第j列中的数字.最初我们有A [i,j] = 0(1 <= i,j <= N). 我们可以通过以下方式更改矩阵.给 ...
- 练习题|MySQL
MySQL主要内容: 1.数据库介绍.类型.特性2.MySQL数据库安装.连接.启动.停止3.表字段类型介绍.主键约束.表创建语句4.常用增删改查语句.分组.聚合5.外键管理.unique字段.表结构 ...
- 【Java】 剑指offer(60) n个骰子的点数
本文参考自<剑指offer>一书,代码采用Java语言. 更多:<剑指Offer>Java实现合集 题目 把n个骰子扔在地上,所有骰子朝上一面的点数之和为s.输入n,打 ...
- adb命令大全
废话不多说,直接adb -help查看所有命令然后翻译 -a - directs adb to listen on all interfaces for a connection 指导adb监听连接的 ...
- Flag之2019年立
今天是2019年1月12日,这是我第一次在一个公众的平台上立flag. 至于为何想立一个flag,应该是因为自己年龄渐长,从儿时读书时代家人对自己的要求就不高,考试可以及格即可,导致了自己养成了比较安 ...
- Java中十个常见的违规编码
摘要:作者Veera Sundar在清理代码工作时发现一些常见的违规编码,因此,Veera Sundar把针对常见的一些违规编码总结成一份列表,以便帮助Java爱好者提高代码的质量和可维护性. 最近, ...