使用內省(introspection)重構策略模式 - Python

Posted by: Max Chen | in Python | 1 year, 1 month ago |

經典設計模式,梳理了一下在Python中使用與重構的思路。

一, 何謂策略模式?

image

定義一系列算法(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
  • 優點 - 算法獨立於呼叫端,降低耦合。
  • 缺點 - discount方法多次重複/調用時(參見三.1),在每個新的上下文 ( Order 實例)中使用相同的策略時不斷新建具體策略對象,增加消耗=> 可以優化

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)
  • 優點 - 自動判斷最佳策略了,即便策略添加新策略!
  • 缺點 - 需要限定命名規則(包含promo) => 可以優化

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)
  • 優點 - 基本上全自動判斷最佳策略了,即便策略添加新策略!
  • 缺點 - 但如果添加的新策略,不是返回訂單折扣(沒有定義抽象類的trade-"off"),那此調用時就會出錯 => 可以優化 => 開發者必須約定此策略新增的函數以及在實例調用時參數實在加以判斷。

動態收集促銷折扣函數更為常見的一種方案是使用簡單的裝飾器,寫到裝飾器時再回來一併寫。

以上範例出自:“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

Currently unrated
 or 

Subscribe

* indicates required

Recent Posts

Archive

2023
2022
2021

Categories

Apache 1

Data Science 2

Dbfit 1

Design Pattern 1

Devops 4

DigitalOcean 1

Django 1

English 3

Excel 5

FUN 4

Flask 3

Git 1

HackMD 1

Heroku 1

Html/Css 1

Linux 4

MDX 1

Machine Learning 2

Manufacture 1

Master Data Service 1

Mezzanine 18

Oracle 1

Postgresql 7

PowerBI 4

Powershell 4

Python 22

SEO 2

SQL Server 53

SQL Server Analytics Service 1

SQLite 1

Windows 1

database 8

work-experience 1

其他 1

投資入門 1

投資心得 2

時間管理 1

總體經濟 2

自我成長 3

資料工程 1

Tags

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)

Authors

Max Chen (159)

Feeds

RSS / Atom

使用內省(introspection)重構策略模式 - Python

© COPYRIGHT 2011-2022. Max的文藝復興. ALL RIGHT RESERVED.