『Fluent Python』学習ノート:6章一等関数を用いて設計モードを実現

35906 ワード

本文は主にFluent Python 6章の学習ノートである.この部分では,主に一等関数を用いてポリシーモードやコマンドモードなどを実現することを紹介した.
『Fluent Python』学習ノート:6章一等関数を用いて設計モードを実現
  • 6.1ケーススタディ:「ポリシー」モードの再構築
  • 6.2「コマンド」モード
  • 巨人の肩
  • 6.1ケーススタディ:「ポリシー」モデルの再構築
    「設計モード:多重化可能なオブジェクト向けソフトウェアの基礎」のポリシーモードのまとめは以下の通りである.
    一連のアルゴリズムを定義し、それらを一つ一つカプセル化し、互いに置き換えることができます.このモードでは、アルゴリズムを使用するお客様とは独立して変更できます.
    電子商取引の分野では、顧客の属性や注文書の商品に基づいて割引を計算するのは、戦略モデルに適している.もしあるネットショップが以下の割引ルールを制定したら:
  • 1000またはそれ以上のポイントのお客様は、注文ごとに5%の割引を受けます.
  • 同じ注文のうち、1つの商品の数は20以上を超え、10%の割引を受けています.
  • 注文の異なる商品は10個以上に達し、7%の割引を受けます.

  • 簡単に言えば、1つの注文で1つの割引しか受けられないと仮定します.
    分析:コンテキスト(Context):いくつかの計算を異なるアルゴリズムを実現する交換可能なコンポーネントに委託し、サービスを提供する.この電子商取引の例では、コンテキストはOrderであり、異なるアルゴリズムに基づいて販促割引を計算する.ポリシー(Strategy):異なるアルゴリズムを実装するコンポーネント共通インタフェース.この例では、promotionという抽象クラスがこの役割を果たします.具体的なポリシー(Concrete strategy):「ポリシー」の具体的なサブクラス、FidelityPromo、BulkPromo、LargeOrderPromoは、ここで実装される3つの具体的なポリシーです.
    具体的なポリシーは、コンテキストクラスの顧客によって選択されます.この例では、受注をインスタンス化する前に、システムは何らかの方法で販促割引ポリシーを選択し、Order構築方法に渡します.具体的にどのようにポリシーを選択するかは、このモードの職責範囲内ではありません.
    #    Order  ,         
    from abc import ABC, abstractmethod
    from collections import namedtuple
    
    Customer = namedtuple('Customer', 'name fidelity')
    
    
    class LineItem(object):
    
        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(object):  #    
    
        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 = ''
            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
    
    
    def large_order_promo(order):  #        
        """         10    7%  """
        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)  # joe    0
    ann = Customer('Ann Smith', 1100)  # ann    1100
    cart = [
        LineItem('banana', 4, .5),  #          
        LineItem('apple', 10, 1.5),
        LineItem('watermellon', 5, 5.0)
    ]
    print(Order(joe, cart, FidelityPromo()))  # FidelityPromo    joe     
    print(Order(ann, cart, FidelityPromo()) ) # ann    5%  
    banana_cart = [
        LineItem('banana', 30, .5),
        LineItem('apple', 10, 1.5)
    ]
    print(Order(joe, banana_cart, BulkItemPromo()))  #    joe    1.5  
    long_order = [LineItem(str(item_code), 1, 1.0) for item_code in range(10)]  # long_order  10      ,      1  
    print(Order(joe, long_order, LargeOrderPromo()))  #  joe        7%  
    print(Order(joe, cart, LargeOrderPromo()))
    
    
    
    
    
    
    

    上記の例は完全に利用可能であるが,Pythonにおけるオブジェクトとしての関数を用いて,より少ないコードを用いて同じ機能を実現できる.
    関数を使用して「ポリシー」モードを実装します.上記の例では、特定のポリシーは個別のクラスであり、各クラスにはメソッドが1つしかなく、インスタンス属性がありません.そのため、特定のポリシーを単純な関数に変更し、Promo抽象クラスを削除できます.次のように再構築されます.
    #    Order  ,         
    from collections import namedtuple
    
    Customer = namedtuple('Customer', 'name fidelity')
    
    
    class LineItem(object):
    
        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(object):  #    
    
        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)  #         self.promotion()  
            return self.total() - discount
    
        def __repr__(self):
            fmt = ''
            return fmt.format(self.total(), self.due())
    
    
    
    def fidelity_promo(order):  #        
        """  1000     5%  """
        return order.total() * .05 if order.customer.fidelity >= 1000 else 0
    
    def bulk_item_promo(order):  #      
        """    20    10%  """
        discount = 0
        for item in order.cart:
            if item.quantity >= 20:
                discount += item.total() * .1
        return discount
    
    def large_order_promo(order):  #        
        """         10    7%  """
    
        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)  # joe    0
    ann = Customer('Ann Smith', 1100)  # ann    1100
    cart = [
        LineItem('banana', 4, .5),  #          
        LineItem('apple', 10, 1.5),
        LineItem('watermellon', 5, 5.0)
    ]
    print(Order(joe, cart, fidelity_promo))  # FidelityPromo    joe     
    print(Order(ann, cart, fidelity_promo) ) # ann    5%  
    banana_cart = [
        LineItem('banana', 30, .5),
        LineItem('apple', 10, 1.5)
    ]
    print(Order(joe, banana_cart, bulk_item_promo))  #    joe    1.5  
    long_order = [LineItem(str(item_code), 1, 1.0) for item_code in range(10)]  # long_order  10      ,      1  
    print(Order(joe, long_order, large_order_promo))  #  joe        7%  
    print(Order(joe, cart, large_order_promo))
    
    
    
    
    
    
    

    ポリシー・オブジェクトは通常、良いエンティティです.エンティティ:共有可能なオブジェクトのみで、複数のコンテキストで同時に使用できます.共有は推奨されます.
    さっきのこの電子商取引の例をさらに最適化します.最適な戦略を選択します:best_promo関数を追加します
    # best_promo         ,        
    promos = [fidelity_promo, bulk_item_promo, large_order_promo]  #             
    
    def best_promo(order):
        """      """
        return max(promo(order) for promo in promos)
    

    さらに最適化するとglobals()は、現在のグローバルシンボルテーブルを表す辞書を返します.これにより、新しい販促ポリシーが追加された後、手動で変更する必要がなくなります.promos.
    #    promos   
    promos = [globals()[name] for name in globals() if name.endswith('_promo') and name != 'best_promo']
    
    def best_promo(order):
        """      """
        return max(promo(order) for promo in promos)
    

    6.2「コマンド」モード
    「コマンド」モードの目的は、呼び出し操作のオブジェクト(呼び出し元)とインプリメンテーションを提供するオブジェクト(受信者)の結合を解除することです.たとえば、呼び出し元はグラフィックアプリケーションのメニュー項目であり、受信者は編集されたドキュメントまたはアプリケーション自体です.このモードでは、両者の間にCommandオブジェクトを配置し、1つのメソッド(execute)のみをインプリメンテーションするようにしますのインタフェースで、受信者のメソッドを呼び出して必要な操作を実行します.これにより、呼び出し者は受信者のインタフェースを理解する必要がなく、異なる受信者は異なるCommandサブクラスに適応することができます.
    巨人の肩
  • 《Fluent Python》
  • 『流暢なPython』