經典設計模式,梳理了一下在Python中使用與重構的思路。
定義一系列算法(fidelitypromo.discount/bulkitempromo.discont/largeorderpromo.discount),把它們一一封裝起來(FidelityPromo/BulkitemPromo/LargeOrderPromo),並且使它們可以相互替換(Promotion)。策略模式使得算法可以獨立於使用它的客戶而變化。
1.基於抽象類實現策略模式:
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)
return self.total() - discount
def __repr__(self):
fmt = '<Order total: {:.2f} due: {:.2f}>'
return fmt.format(self.total(), self.due())
###
class Promotion(ABC): # 策略:抽象類
@abstractmethod
def discount(self, order):
"""返回則扣金額(正值)"""
class fidelity_promo(promo):
def discount(self.order):
"""5% discount for customers with 1000 or more fidelity points"""
return order.total() * .05 if order.customer.fidelity >= 1000 else 0
class bulk_item_promo(promo):
def discount(self.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
class large_order_promo(promo):
def discount(self.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
2.基於Python一等函數特性實現策略模式:
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)
return self.total() - discount
def __repr__(self):
fmt = '<Order total: {:.2f} due: {:.2f}>'
return fmt.format(self.total(), self.due())
def fidelity_promo(order):
"""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
不需定義抽象類,並使用函數替換類,調用時,只需把促銷函數作為參數傳入,不必在新建訂單時實例化新的促銷對象,函數拿來即用。
接下來,看看如何調用以及調用是否可以優化。
1.直接調用(對應的是基於抽象類實現策略模式):
>>> joe = Customer('John Doe', 0)
>>> ann = Customer('Ann Smith', 1100)
>>> cart = [LineItem('banana', 4, .5),LineItem('apple', 10, 1.5),LineItem('watermellon', 5, 5.0)]
>>> Order(joe, cart, FidelityPromo())
<Order total: 42.00 due: 42.00>
>>> Order(ann, cart, FidelityPromo())
<Order total: 42.00 due: 39.90>
>>> banana_cart = [LineItem('banana', 30, .5),LineItem('apple', 10, 1.5)]
>>> Order(joe, banana_cart, BulkItemPromo())
<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, LargeOrderPromo())
<Order total: 10.00 due: 9.30>
>>> Order(joe, cart, LargeOrderPromo())
<Order total: 42.00 due: 42.00>
2.建立一個選擇最佳策略函數(以下對應的是"基於Python一等函數特性實現策略模式"):
promos = [fidelity_promo, bulk_item_promo, large_order_promo]
def best_promo(order):
"""Select best discount available
"""
return max(promo(order) for promo in promos)
3.建立一個選擇最佳策略函數,使用內省globals()
promos = [globals()[name] for name in globals()
if name.endswith('_promo')
and name != 'best_promo']
def best_promo(order):
"""Select best discount available
"""
return max(promo(order) for promo in promos)
4.建立一個選擇最佳策略函數,使用內省inspect:
promos = [func for name, func in
inspect.getmembers(promotions, inspect.isfunction)]
def best_promo(order):
"""Select best discount available
"""
return max(promo(order) for promo in promos)
動態收集促銷折扣函數更為常見的一種方案是使用簡單的裝飾器,寫到裝飾器時再回來一併寫。
以上範例出自:“Fluent Python by Luciano Ramalho (O’Reilly). Copyright 2015 Luciano Ramalho, 978-1-491-94600-8.”
Reference:
https://zh.wikipedia.org/wiki/%E5%A4%B4%E7%AD%89%E5%87%BD%E6%95%B0
https://ithelp.ithome.com.tw/articles/10202506
https://www.books.com.tw/products/0010706172
https://zh.wikipedia.org/zh-tw/%E7%AD%96%E7%95%A5%E6%A8%A1%E5%BC%8F
https://skyyen999.gitbooks.io/-study-design-pattern-in-java/content/strategy.html
Share on Twitter Share on FacebookSQL Server Analytics Service 1
SEO(1) Github(2) Title Tag(2) ML(1) 李宏毅(1) SQL Server(18) Tempdb(1) SSMS(1) Windows(1) 自我成長(2) Excel(1) python Flask(1) python(5) Flask(2)
Max Chen (159)