1. 程式人生 > >流暢的python 使用一等函數實現設計模式

流暢的python 使用一等函數實現設計模式

lis 函數列表 金額 怎麽 編輯 領域 motion and 只有一個

案例分析:重構“策略”模式

經典的“策略”模式

技術分享圖片

電商領域有個功能明顯可以使用“策略”模式,即根據客戶的屬性或訂單
中的商品計算折扣。
假如一個網店制定了下述折扣規則。

  • 有 1000 或以上積分的顧客,每個訂單享 5% 折扣。
  • 同一訂單中,單個商品的數量達到 20 個或以上,享 10% 折扣。
  • 訂單中的不同商品達到 10 個或以上,享 7% 折扣。

簡單起見,我們假定一個訂單一次只能享用一個折扣。

上下文
  把一些計算委托給實現不同算法的可互換組件,它提供服務。在這個電商示例中,上下文是 Order,它會根據不同的算法計算促銷折扣。


策略
  實現不同算法的組件共同的接口。在這個示例中,名為 Promotion的抽象類扮演這個角色。

具體策略
  “策略”的具體子類。fidelityPromo、BulkPromo 和LargeOrderPromo 是這裏實現的三個具體策略。

在這個示例中,實例化訂單之前,系統會以某種方式選擇一種促銷折扣策略,然後把它傳給 Order 構造方法。具體怎麽選擇策略,不在這個模式的職責範圍內。

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: # 上下文 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) : # 策略:抽象基類 @abstractmethod def discount(self, order): """返回折扣金額(正值)""" class FidelityPromo(Promotion): # 第一個具體策略 """為積分為1000或以上的顧客提供5%折扣""" def discount(self, order): return order.total() * .05 if order.customer.fidelity >= 1000 else 0 class BulkItemPromo(Promotion): # 第二個具體策略 """單個商品為20個或以上時提供10%折扣""" def discount(self, order): discount = 0 for item in order.cart: if item.quantity >= 20: discount += item.total() * .1 return discount class LargeOrderPromo(Promotion): # 第三個具體策略 """訂單中的不同商品達到10個或以上時提供7%折扣""" def discount(self, order): distinct_items = {item.product for item in order.cart} if len(distinct_items) >= 10: return order.total() * .07 return 0

在某個實現了上述規則的模塊中演示和驗證相關操作。

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

? 兩個顧客:joe 的積分是 0,ann 的積分是 1100。
? 有三個商品的購物車。
? fidelityPromo 沒給 joe 提供折扣。
? ann 得到了 5% 折扣,因為她的積分超過 1000。
? banana_cart 中有 30 把香蕉和 10 個蘋果。
? BulkItemPromo 為 joe 購買的香蕉優惠了 1.50 美元。
? long_order 中有 10 個不同的商品,每個商品的價格為 1.00 美元。
? LargerOrderPromo 為 joe 的整個訂單提供了 7% 折扣。
示例 6-1 完全可用,但是利用 Python 中作為對象的函數,可以使用更少
的代碼實現相同的功能。詳情參見下一節。

“命令”模式

“命令”設計模式也可以通過把函數作為參數傳遞而簡化。這一模式對類的編排如圖

技術分享圖片

各個命令可以有不同的接收者(實現操作的對象)。對
PasteCommand 來說,接收者是 Document。對 OpenCommand 來說,接收者是應用程序
“命令”模式的目的是解耦調用操作的對象(調用者)和提供實現的對象(接收者)。在《設計模式:可復用面向對象軟件的基礎》所舉的示例中,調用者是圖形應用程序中的菜單項,而接收者是被編輯的文檔或應用程序自身。

這個模式的做法是,在二者之間放一個 Command 對象,讓它實現只有一個方法(execute)的接口,調用接收者中的方法執行所需的操作。這樣,調用者無需了解接收者的接口,而且不同的接收者可以適應不同的 Command 子類。調用者有一個具體的命令,通過調用 execute 方法執行。註意,圖中的 MacroCommand 可能保存一系列命令,它的execute() 方法會在各個命令上調用相同的方法。

我們可以不為調用者提供一個 Command 實例,而是給它一個函數。此時,調用者不用調用 command.execute(),直接調用 command() 即可。MacroCommand 可以實現成定義了 __call__ 方法的類。這
樣,MacroCommand 的實例就是可調用對象,各自維護著一個函數列表,供以後調用,

class MacroCommand:
"""一個執行一組命令的命令"""
    def __init__(self, commands):
        self.commands = list(commands) # ?
    def __call__(self):
        for command in self.commands: # ?
            command()

? 使用 commands 參數構建一個列表,這樣能確保參數是可叠代對象,還能在各個 MacroCommand 實例中保存各個命令引用的副本。

? 調用 MacroCommand 實例時,self.commands 中的各個命令依序執行。

使用一等函數對“命令”模式的重新審視到此結束。站在一定高度上看,這裏采用的方式與“策略”模式所用的類似:把實現單方法接口的類的實
例替換成可調用對象。畢竟,每個 Python 可調用對象都實現了單方法接口,這個方法就是 __call__。





流暢的python 使用一等函數實現設計模式