• [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
    

    点评

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

    策略模式

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

    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)
    
    • 简单工厂需要让客户端认识两个类,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,这显得并不优雅,任何需要的变更都需要成本,但是成本的高低是有差异的,为了更加优雅,降低变更成本,可以使用反射技术,这一技术将在抽象工厂模式中介绍。

  • 相关阅读:
    缓冲区溢出攻击练习
    小例子背后的大道理——从DIP中“倒置”的含义说接口的正确使用
    关于.NET里String.Compare与C++的不同,请高人解释下为什么这样?
    详解Adorner Layer
    百度文库手机版通信协议分析
    这段.NET Framework的源代码似乎有很大的优化空间
    在ListView的GroupItem头中显示每列的Summary
    随机抽奖
    MySQL的多表查询(笛卡尔积原理) 1
    采用Apache服务器的虚拟主机设置伪静态教程
  • 原文地址:https://www.cnblogs.com/CheeseZH/p/9368595.html
Copyright © 2020-2023  润新知