重構電商例子(策略模式)
阿新 • • 發佈:2018-02-13
obj item com cached 圖片 子類 tom appid ems
案例分析:重構“策略”模式
《設計模式:可復用面向對象軟件的基礎》一書是這樣概述“策略”模式的:
定義一系列算法,把它們一一封裝起來,並且使它們可以相互替換。本模式使得算法可以獨立於使用它的客戶而變化。
假如一個網店制定了下述折扣規則:
- 有 1000 或以上積分的顧客,每個訂單享 5% 折扣。
- 同一訂單中,單個商品的數量達到 20 個或以上,享 10% 折扣。
- 訂單中的不同商品達到 10 個或以上,享 7% 折扣。
- 簡單起見,我們假定一個訂單一次只能享用一個折扣
“策略”模式的 UML 類圖見圖 6-1,其中涉及下列內容。
內容 | 說明 |
---|---|
上下文 | 把一些計算委托給實現不同算法的可互換組件,它提供服務。在這個電商示例中,上下文是Order,它會根據不同的算法計算促銷折扣。 |
策略 | 實現不同算法的組件共同的接口。在這個示例中,名為 Promotion 的抽象類扮演這個角色。 |
具體策略 | “策略”的具體子類。fidelityPromo、BulkPromo 和 LargeOrderPromo 是這裏實現的三個具體策略。 |
實現 Order 類,支持插入式折扣策略
# classic_strategy.py # Strategy pattern -- classic implementation """ # BEGIN CLASSIC_STRATEGY_TESTS >>> joe = Customer(‘John Doe‘, 0) # <1> >>> ann = Customer(‘Ann Smith‘, 1100) >>> cart = [LineItem(‘banana‘, 4, .5), # <2> ... LineItem(‘apple‘, 10, 1.5), ... LineItem(‘watermellon‘, 5, 5.0)] >>> Order(joe, cart, FidelityPromo()) # <3> <Order total: 42.00 due: 42.00> >>> Order(ann, cart, FidelityPromo()) # <4> <Order total: 42.00 due: 39.90> >>> banana_cart = [LineItem(‘banana‘, 30, .5), # <5> ... LineItem(‘apple‘, 10, 1.5)] >>> Order(joe, banana_cart, BulkItemPromo()) # <6> <Order total: 30.00 due: 28.50> >>> long_order = [LineItem(str(item_code), 1, 1.0) # <7> ... for item_code in range(10)] >>> Order(joe, long_order, LargeOrderPromo()) # <8> <Order total: 10.00 due: 9.30> >>> Order(joe, cart, LargeOrderPromo()) <Order total: 42.00 due: 42.00> # END CLASSIC_STRATEGY_TESTS """ # BEGIN CLASSIC_STRATEGY from abc import ABC, abstractmethod from collections import namedtuple Customer = namedtuple(‘Customer‘, ‘name fidelity‘) class LineItem: def __init__(self, product, quantity, price): self.product = product self.quantity = quantity self.price = price def total(self): return self.price * self.quantity class Order: # the Context def __init__(self, customer, cart, promotion=None): self.customer = customer self.cart = list(cart) self.promotion = promotion def total(self): if not hasattr(self, ‘__total‘): self.__total = sum(item.total() for item in self.cart) return self.__total def due(self): if self.promotion is None: discount = 0 else: discount = self.promotion.discount(self) return self.total() - discount def __repr__(self): fmt = ‘<Order total: {:.2f} due: {:.2f}>‘ return fmt.format(self.total(), self.due()) class Promotion(ABC): # the Strategy: an Abstract Base Class @abstractmethod def discount(self, order): """Return discount as a positive dollar amount""" class FidelityPromo(Promotion): # first Concrete Strategy """5% discount for customers with 1000 or more fidelity points""" def discount(self, order): return order.total() * .05 if order.customer.fidelity >= 1000 else 0 class BulkItemPromo(Promotion): # second Concrete Strategy """10% discount for each LineItem with 20 or more units""" def discount(self, order): discount = 0 for item in order.cart: if item.quantity >= 20: discount += item.total() * .1 return discount class LargeOrderPromo(Promotion): # third Concrete Strategy """7% discount for orders with 10 or more distinct items""" def discount(self, order): distinct_items = {item.product for item in order.cart} if len(distinct_items) >= 10: return order.total() * .07 return 0 # END CLASSIC_STRATEGY
使用函數實現“策略”模式
每個具體策略都是一個類,而且都只定義了一個方法,即discount。此外,策略實例沒有狀態(沒有實例屬性)。
提示
當一個類只有一個函數時,應該將其重構成一個函數。因爲函數的開銷比類小很多。
# strategy.py # Strategy pattern -- function-based implementation """ # BEGIN STRATEGY_TESTS >>> joe = Customer(‘John Doe‘, 0) # <1> >>> ann = Customer(‘Ann Smith‘, 1100) >>> cart = [LineItem(‘banana‘, 4, .5), ... LineItem(‘apple‘, 10, 1.5), ... LineItem(‘watermellon‘, 5, 5.0)] >>> Order(joe, cart, fidelity_promo) # <2> <Order total: 42.00 due: 42.00> >>> Order(ann, cart, fidelity_promo) <Order total: 42.00 due: 39.90> >>> banana_cart = [LineItem(‘banana‘, 30, .5), ... LineItem(‘apple‘, 10, 1.5)] >>> Order(joe, banana_cart, bulk_item_promo) # <3> <Order total: 30.00 due: 28.50> >>> long_order = [LineItem(str(item_code), 1, 1.0) ... for item_code in range(10)] >>> Order(joe, long_order, large_order_promo) <Order total: 10.00 due: 9.30> >>> Order(joe, cart, large_order_promo) <Order total: 42.00 due: 42.00> # END STRATEGY_TESTS """ # BEGIN STRATEGY from collections import namedtuple Customer = namedtuple(‘Customer‘, ‘name fidelity‘) class LineItem: def __init__(self, product, quantity, price): self.product = product self.quantity = quantity self.price = price def total(self): return self.price * self.quantity class Order: # the Context def __init__(self, customer, cart, promotion=None): self.customer = customer self.cart = list(cart) self.promotion = promotion def total(self): if not hasattr(self, ‘__total‘): self.__total = sum(item.total() for item in self.cart) return self.__total def due(self): if self.promotion is None: discount = 0 else: discount = self.promotion(self) # <1> return self.total() - discount def __repr__(self): fmt = ‘<Order total: {:.2f} due: {:.2f}>‘ return fmt.format(self.total(), self.due()) # <2> def fidelity_promo(order): # <3> """5% discount for customers with 1000 or more fidelity points""" return order.total() * .05 if order.customer.fidelity >= 1000 else 0 def bulk_item_promo(order): """10% discount for each LineItem with 20 or more units""" discount = 0 for item in order.cart: if item.quantity >= 20: discount += item.total() * .1 return discount def large_order_promo(order): """7% discount for orders with 10 or more distinct items""" distinct_items = {item.product for item in order.cart} if len(distinct_items) >= 10: return order.total() * .07 return 0 # END STRATEGY
選擇最佳策略:簡單的方式
promos = [fidelity_promo, bulk_item_promo, large_order_promo] # <1>promos 列出以函數實現的各個策略。
def best_promo(order):
"""Select best discount available
"""
return max(promo(order) for promo in promos)
但是有些重復可能會導致不易察覺的缺陷:若想添加新的促銷策略,要定義相應的函數,還要記得把它添加到promos列表中;否則,當新促銷函數顯式地作為參數傳給 Order 時,它是可用的,但是 best_promo 不會考慮它。
找出模塊中的全部策略
promos = [globals()[name] for name in globals() # <1>叠代 globals() 返回字典中的各個 name。
if name.endswith(‘_promo‘) # <2>只選擇以 _promo 結尾的名稱。
and name != ‘best_promo‘] # <3>過濾掉 best_promo 自身,防止無限遞歸。
def best_promo(order):
"""Select best discount available
"""
return max(promo(order) for promo in promos) # <4>best_promo 內部的代碼沒有變化。
這裏需要註意的地方有:
- 策略模式所定義的函數結尾均是
_promo
- 不要忘記類
globals()
globals()
from pprint import pprint
def fun():
pass
dd = dict()
ll = list()
pprint(globals())
"""output
{‘__annotations__‘: {},
‘__builtins__‘: <module ‘builtins‘ (built-in)>,
‘__cached__‘: None,
‘__doc__‘: None,
‘__file__‘: ‘/home/yuanoung/Projects/fluent-python/other/test.py‘,
‘__loader__‘: <_frozen_importlib_external.SourceFileLoader object at 0x7f35590ca080>,
‘__name__‘: ‘__main__‘,
‘__package__‘: None,
‘__spec__‘: None,
‘dd‘: {},
‘fun‘: <function fun at 0x7f35590f0ea0>,
‘ll‘: [],
‘pprint‘: <function pprint at 0x7f355735b840>}
"""
重構電商例子(策略模式)