スムーズなpython読書ノート-第6章-一等関数を用いて設計モードを実現


一等関数実現設計モード
古典的な「ポリシー」モード定義
一連のアルゴリズムを定義し、それらを一つ一つカプセル化し、互いに置き換えることができます.このモードでは、アルゴリズムを使用するお客様とは独立して変更できます.
ケース
もしあるネットショップが下記の割引ルールを制定したら.
  • に1000以上のポイントがあるお客様は、注文ごとに5%割引を受けます.
  • 同じ注文のうち、1つの商品の数は20個以上に達し、10%の割引を受けています.
  • 注文の異なる商品は10個以上に達し、7%の割引を受けます.

  • 簡単に言えば、1つの注文で1回に1つの割引しか受けられないと仮定します.
  • 特定のポリシーは、コンテキストクラスのお客様によって選択されます.
  • 受注をインスタンス化する前に、システムは何らかの方法で販促割引ポリシーを選択します.
  • そしてそれをOrder構造方法に伝える.
  • 具体的にどのように戦略を選択するかは、このモデルの職責の範囲内ではありません.
  • from abc import ABC, abstractclassmethod
    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
    
    
    #      Order
    class Order(object):
        def __init__(self, customer, cart, promotion=None):
            self.customer = customer
            self.cart = list(cart)
            self.promotion = promotion
    
        def total(self):  ###4
            if not hasattr(self, '__total'):
                self.__total = sum(item.total() for item in self.cart)
            return self.__total
    
        def due(self):  ###5
            if self.promotion is None:
                discount = 0
            else:
                discount = self.promotion.discount(self)  ###6
            return self.total() - discount
    
        def __repr__(self):  ###2
            fmt = ''
            return fmt.format(self.total(), self.due())  ###3
    
    
    #   :    
    class Promotion(ABC):
        @abstractclassmethod
        def discount(self, order):
            """      (  )"""
    
    
    class FidelityPromo(Promotion):  #        
    
        """    1000        5%  """
    
        def discount(self, order):  ###7
            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
    
    
    if __name__ == "__main__":
        #           0    1100
        longe = Customer('longe', 0)
        liang = Customer('Ann Smith', 1100)
    
        #      
        cart = [LineItem('banana', 4, .5), LineItem('apple', 10, 1.5), LineItem('watermellon', 5, 5.0)]
        #      
        print(Order(longe, cart, FidelityPromo()))  ###111
    
        print(Order(liang, cart, FidelityPromo()))
    
        banana_cart = [LineItem('banana', 30, .5), LineItem('apple', 10, 1.5)]
    
        print(Order(longe, banana_cart, BulkItemPromo()))
    
        long_order = [LineItem(str(item_code), 1, 1.0) for item_code in range(10)]
        
        print(Order(longe, long_order, LargeOrderPromo()))
    

    関数を使用したポリシー・モードの実装
    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(self)
            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)
    ann = Customer('Ann Smith', 1100)
    cart = [LineItem('banana', 4, .5),LineItem('apple', 10, 1.5),LineItem('watermellon', 5, 5.0)]
    
    print(Order(joe, cart, fidelity_promo))
    
    print(Order(ann, cart, fidelity_promo))
    
    banana_cart = [LineItem('banana', 30, .5),LineItem('apple', 10, 1.5)]
    print(Order(joe, banana_cart, bulk_item_promo))
    
    long_order = [LineItem(str(item_code), 1, 1.0) for item_code in range(10)]
    print(Order(joe, long_order, large_order_promo))
    
    print(Order(joe, cart, large_order_promo))

    戦略モデル思想
  • ポリシーオブジェクトは、通常、flyweight
  • である.
  • 享元は共有可能なオブジェクトであり、複数のコンテキストで同時に使用することができる.
  • 共有は、新しいコンテキスト(ここではOrderインスタンス)ごとに同じポリシーを使用する必要がない場合に、特定のポリシーオブジェクトを絶えず新規作成する必要がなく、消費量を削減するために推奨されます.
  • 複雑な状況では、
  • 特定のポリシーが内部状態を維持する必要がある場合、「ポリシー」と「享元」モードを結合する必要がある場合があります.
  • しかし、特定のポリシーには一般的に内部状態はなく、コンテキスト内のデータを処理するだけである.この場合、通常の関数を使用して、1つのメソッドしかないクラスを記述しないで、別のクラスが宣言する単一の関数インタフェースを実現しなければなりません.
  • 関数は、ユーザー定義クラスのインスタンスよりも軽量であり、各ポリシー関数はPythonでモジュールをコンパイルするときに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:  #    
        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 = ''
    
            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)
    ann = Customer('Ann Smith', 1100)
    
    cart = [LineItem('banana', 4, .5), LineItem('apple', 10, 1.5), LineItem('watermellon', 5, 5.0)]
    banana_cart = [LineItem('banana', 30, .5), LineItem('apple', 10, 1.5)]
    long_order = [LineItem(str(item_code), 1, 1.0) for item_code in range(10)]
    
    
    ##        
    promos = [fidelity_promo, bulk_item_promo, large_order_promo]
    
    def best_promo(order):
        """         """
        # print([promo(order) for promo in promos])
        return max(promo(order) for promo in promos)
    
    print(Order(joe, long_order, best_promo))
    
    print(Order(joe, banana_cart, best_promo))
    
    print(Order(ann, cart, best_promo) )

    モジュール内のすべてのポリシーをコードを変更したくない
  • 新しいポリシー名の末尾は_promo
  • globals関数を使用してbest_を支援Promoは自動的に他の利用可能な*_を見つけますPromo関数、プロセスは少し曲がりくねっています
  • 内省モジュールのグローバルネーミングスペース、promosリスト
  • を構築
  • best_をフィルタpromo自身、無限再帰を防ぐ.
  • promos = [globals()[name] for name in globals() if name.endswith('_promo') and name != 'best_promo']
    
    print(promos)
    

    別の方法
    もう1つの実行可能な方法は、best_を除いて、すべてのポリシー関数を別のモジュールに格納することである.promo、ここでは3つのポリシー関数をpromotionsに保存します.py中
    次のコードでは,最大の変化時にpromotionsという独立モジュールを省き,ポリシー関数リストを構築する.注意、promotionsモジュールと高次内省関数のinspectモジュールをインポートします.
    import  inspect
    import promotions
    promos = [func for name, func in
              inspect.getmembers(promotions, inspect.isfunction)]
    
    
    def best_promo(order):
        """          """
        return max(promo(order) for promo in promos)
    
    print(Order(joe, long_order, best_promo))
    
    print(Order(joe, banana_cart, best_promo))
    
    print(Order(ann, cart, best_promo) )

    コマンドモード
  • コマンドモードの目的は、呼び出し動作のオブジェクト(呼び出し者)と、実装されたオブジェクト(受信者)
  • とをデカップリングすることである.
  • では、「設計モード:オブジェクト向けソフトウェアの多重化可能なベース」に挙げられる例では、呼び出し元はグラフィックアプリケーションのメニュー項目であり、受信者は編集されたドキュメントまたはアプリケーション自体である.

  • パターンの作り方
  • このモードでは、両方の間にCommandオブジェクトを配置し、受信者のメソッドを呼び出して必要な操作を実行する1つのメソッド(execute)のみのインタフェースを実現させる.
  • により、呼び出し元は受信者のインタフェースを理解する必要がなく、異なる受信者は異なるCommandサブクラスに適応することができる.
  • 呼び出し者は、executeメソッドを呼び出すことによって実行される特定のコマンドを有する.

  • MacroCommandの各インスタンスにはコマンドリストが内部に格納されています
    class MacroCommand:
    """           """
        def __init__(self, commands):
            self.commands = list(commands)
        def __call__(self):
            for command in self.commands:
            command()

    まとめ
    1.pythonは、クラスを書くことなく、いくつかの設計のデフォルトを純粋な関数で実現することができます.デザインパターンを見なければなりません