[Python設計模式] 第2章 商場收銀軟件——策略模式

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

題目

設計一個控制檯程序, 模擬商場收銀軟件,根據客戶購買商品的單價和數量,計算總價。git

基礎版本

price = float(input("輸入商品單價:"))
number = int(input("輸入商品數量:"))
total = (price * number)
print("當前總價: %.2f" % total)
輸入商品單價:40
輸入商品數量:9
當前總價: 360.00

點評

上述程序僅僅實現了基本功能,可是當商場有打折活動,例如八折,五折等,就不知足需求了,折扣的方法還可能有滿減活動,例如滿300減100,滿500減200等。假設只有打折和滿減兩種促銷活動,那麼這就很像上一章節的計算器,支持正常收費,打折活動和滿減活動三種計算方法,能夠用簡單工廠方法實現。github

改進版本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,這顯得並不優雅,任何須要的變動都須要成本,可是成本的高低是有差別的,爲了更加優雅,下降變動成本,可使用反射技術,這一技術將在抽象工廠模式中介紹。spa

相關文章
相關標籤/搜索