程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式師世界 >> 編程語言 >  >> 更多編程語言 >> Python

[python] using first-class functions to implement design patterns

編輯:Python

《 smooth Python》 Luciano · Ramallo The first 6 Chapter   Use first-class functions to implement design patterns   Reading notes

6.1 case analysis : restructure “ Strategy ” Pattern

If the function as a first-class object is reasonably used , Some design patterns can be simplified ,“ Strategy ” Patterns are one example .

6.1.1 classical “ Strategy ” Pattern

《 Design patterns : The foundation of reusable object-oriented software 》 The book is summarized as follows “ Strategy ” Mode :
Define a series of algorithms , Put them all together , And make them interchangeable . This pattern allows the algorithm to vary independently of the client who uses it . There is an obvious function in the e-commerce field that can be used “ Strategy ” Pattern , That is, the discount is calculated according to the customer's attributes or the goods in the order .


Context
Delegate some calculations to interchangeable components that implement different algorithms , It provides services . In this e-commerce example , The context is Order, It will calculate the promotion discount according to different algorithms .
Strategy
Common interface of components implementing different algorithms . In this example , be known as Promotion Abstract classes play this role .
Specific strategies
Concrete subclasses of policies .fidelityPromo、BulkPromo and LargeOrderPromo Here are three specific strategies .

If an online store makes the following discount rules , Suppose an order can only enjoy one discount at a time
Yes 1000 Customers with points or above , Enjoy... Per order 5% discount .
In the same order , The quantity of a single commodity reaches 20 One or more , enjoy 10% discount .
The different items in the order reach 10 One or more , enjoy 7% discount .

【 Improvement steps 】

Example 6-1 Each strategy is a class , And only one method is defined discount
Example 6-3 Replace the specific strategy with a simple function , And removed Promo abstract class
Example 6-6 best_promo Iterate over a function list , And find the one with the largest discount
          shortcoming : If you want to add a new promotion strategy , To define the corresponding function , Also remember to add it to promos In the list
Example 6-7 Use globals Function help best_promo Automatically find other available *_promo function
Example 6-8 Introspection alone promotions modular , structure promos list
Example 7-3 promos The values in the list use promotion Decorator filling . Portal  ->  Function decorators and closures _wy_hhxx-----------------------------------------------------------------------------

Example 6-1 Realization Order class , Support plug-in discount policy

order.py

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: # 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.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) : # Strategy : Abstract base class
@abstractmethod
def discount(self, order):
""" Return discount amount ( Comes at a time )"""
class FidelityPromo(Promotion): # The first specific strategy
""" The integral is 1000 Or more customers 5% discount """
def discount(self, order):
return order.total() * .05 if order.customer.fidelity >= 1000 else 0
class BulkItemPromo(Promotion): # The second specific strategy
""" A single item is 20 When more than one 10% discount """
def discount(self, order):
discount = 0
for item in order.cart:
if item.quantity >= 20:
discount += item.total() * .1
return discount
class LargeOrderPromo(Promotion): # The third specific strategy
""" The different items in the order reach 10 When more than one 7% discount """
def discount(self, order):
distinct_items = {item.product for item in order.cart}
if len(distinct_items) >= 10:
return order.total() * .07
return 0

Be careful ,line30-33 hold Promotion Defined as an abstract base class (Abstract Base Class,ABC), This is done to use @abstractmethod Decorator , This clearly indicates the mode used .

@abstractmethod: Abstract method , contain abstractmethod Class of method cannot be instantiated , Inherited from the abstractmethod Subclasses of methods must replicate all abstractmethod Method of decoration , What is not decorated can not be rewritten

Example 6-2 Using different promotional discounts Order Class example

>>> from order import *
>>> joe = Customer('John Doe', 0) #1
>>> ann = Customer('Ann Smith', 1100)
>>> cart = [LineItem('banana', 4, .5), #2
... LineItem('apple', 10, 1.5),
... LineItem('watermellon', 5, 5.0)]
>>>
>>> Order(joe, cart, FidelityPromo()) #3
<Order total: 42.00 due: 42.00>
>>> Order(ann, cart, FidelityPromo()) #4
<Order total: 42.00 due: 39.90>
>>>
>>> banana_cart = [LineItem('banana', 30, .5), #5
... LineItem('apple', 10, 1.5)]
>>> Order(joe, banana_cart, BulkItemPromo()) #6
<Order total: 30.00 due: 28.50>
>>> long_order = [LineItem(str(item_code), 1, 1.0) #7
... for item_code in range(10)]
>>> Order(joe, long_order, LargeOrderPromo()) #8
<Order total: 10.00 due: 9.30>
>>> Order(joe, cart, LargeOrderPromo())
<Order total: 42.00 due: 42.00>
>>>

explain :

#1 Two customers :joe The integral of is 0,ann The integral of is 1100.
#2 A shopping cart with three items .
#3 fidelityPromo No joe Offer a discount ( integral <1000).
#4 fidelityPromo to ann Provides 5% discount ( integral >1000).
#5 banana_cart There is 30 Mix bananas with 10 An apple .
#6 BulkItemPromo to joe The bananas you bought are on sale 1.50 dollar ( Banana >20).
#7 long_order There is 10 A different commodity , The price of each commodity is 1.00 dollar .
#8 LargerOrderPromo to joe Our entire order provides 7% discount ( Number of different commodities =10).

6.1.2 Use functions to implement “ Strategy ” Pattern

In the example 6-1 in , Each specific strategy is a class , And they only define one method , namely discount. Besides , The policy instance has no status ( No instance properties ).
Example 6-3 Yes, for example 6-1 The refactoring of , Replace the specific strategy with a simple function , And removed Promo abstract class .

Example 6-3 Order Class and discount policy implemented with functions

……
class Order: # Context
……
def due(self):
if self.promotion is None:
discount = 0
else:
discount = self.promotion(self) # To calculate the discount, just call self.promotion() function
return self.total() - discount
……
def fidelity_promo(order): # There are no abstract classes , Each strategy is a function
""" The integral is 1000 Or more customers 5% discount """
return order.total() * .05 if order.customer.fidelity >= 1000 else 0
def bulk_item_promo(order):
""" A single item is 20 When more than one 10% discount """
discount = 0
for item in order.cart:
if item.quantity >= 20:
discount += item.total() * .1
return discount
def large_order_promo(order):
""" The different items in the order reach 10 When more than one 7% discount """
distinct_items = {item.product for item in order.cart}
if len(distinct_items) >= 10:
return order.total() * .07
return 0

Example 6-4 Promotion discount realized by function Order Class example

>>> from order2 import *
>>> 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, fidelity_promo) # In order to apply the discount strategy to Order For instance , Just pass the promotion function as a parameter
<Order total: 42.00 due: 42.00>
>>> Order(ann, cart, fidelity_promo)
<Order total: 42.00 due: 39.90>
>>> banana_cart = [LineItem('banana', 30, .5),
... LineItem('apple', 10, 1.5)]
>>> Order(joe, banana_cart, bulk_item_promo)
<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, large_order_promo)
<Order total: 10.00 due: 9.30>
>>> Order(joe, cart, large_order_promo)
<Order total: 42.00 due: 42.00>
>>>

There is no need to instantiate a new promotion object when creating an order , The function is ready to use .
Sharing is the recommended practice , It doesn't have to be in every new context ( Here is Order example ) When the same policy is used in, new specific policy objects are created continuously , So as to reduce consumption .

Example 6-6 best_promo Iterate over a function list , And find the one with the largest discount

promos = [fidelity_promo, bulk_item_promo, large_order_promo] # promos List the strategies implemented by functions
def best_promo(order):
""" Choose the best discount available """
return max(promo(order) for promo in promos) # Use a generator expression to put order Pass to promos Functions in the list , Return the function with the largest discount 

Test the following ,

>>> from order3 import *
>>>
>>> joe = Customer('John Doe', 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)]
>>>
>>> Order(joe, long_order, best_promo)
<Order total: 10.00 due: 9.30>
>>> Order(joe, banana_cart, best_promo)
<Order total: 30.00 due: 28.50>

shortcoming : If you want to add a new promotion strategy , To define the corresponding function , Also remember to add it to promos In the list ; otherwise , When the new promotion function is explicitly passed as a parameter to Order when , It's available , however best_promo Will not consider it .

6.1.4 Find out all the strategies in the module

stay Python in , Modules are also first-class objects , And the standard library provides functions of several processing modules .
Built in functions globals()  Return a dictionary , Represents the current global symbol table . This symbol table is always for the current module ( For a function or method , The modules that define them , Instead of calling their modules ).
Example 6-7 Use globals Function help best_promo Automatically find other available *_promo function

promos = [globals()[name] for name in globals() #1
if name.endswith('_promo') #2
and name != 'best_promo'] #3
def best_promo(order):
""" Choose the best discount available """
return max(promo(order) for promo in promos) #4

explain :
#1 iteration globals() Return to each in the dictionary name.
#2 Only select with _promo The name at the end .
#3 To filter out best_promo Oneself , Prevent infinite recursion .
#4 best_promo The internal code has not changed .

>>> globals()
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, 'order4': <module 'order4' from '/root/pydir/order4.py'>}
>>>
>>> from order4 import *
>>>
>>> globals()
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, 'order4': <module 'order4' from '/root/pydir/order4.py'>, 'ABC': <class 'abc.ABC'>, 'abstractmethod': <function abstractmethod at 0x7f8033ec6a60>, 'namedtuple': <function namedtuple at 0x7f802c0a4940>, 'Customer': <class 'order4.Customer'>, 'LineItem': <class 'order4.LineItem'>, 'Order': <class 'order4.Order'>, 'fidelity_promo': <function fidelity_promo at 0x7f802c0a5670>, 'bulk_item_promo': <function bulk_item_promo at 0x7f802c0a54c0>, 'large_order_promo': <function large_order_promo at 0x7f802c0a5430>, 'promos': [<function fidelity_promo at 0x7f802c0a5670>, <function bulk_item_promo at 0x7f802c0a54c0>, <function large_order_promo at 0x7f802c0a5430>], 'best_promo': <function best_promo at 0x7f802c0a53a0>}
>>>
>>> promos
[<function fidelity_promo at 0x7f802c0a5670>, <function bulk_item_promo at 0x7f802c0a54c0>, <function large_order_promo at 0x7f802c0a5430>]
>>>

Another way to collect all available promotions is , Save all policy functions in a separate module , hold best_promo Ruled out .

Example 6-8 Introspection alone promotions modular , structure promos list

Put all three promos The function is placed in promotions.py

order5.py Remove three promos function , And import promotions Modules and provide higher-order introspective functions inspect modular

from abc import ABC, abstractmethod
from collections import namedtuple
import inspect
import promotions
……
promos = [func for name, func in
inspect.getmembers(promotions, inspect.isfunction)]
def best_promo(order):
""" Choose the best discount available """
return max(promo(order) for promo in promos)

Test the following :

>>> import order5
>>> promos
[<function fidelity_promo at 0x7f802c0a5670>, <function bulk_item_promo at 0x7f802c0a54c0>, <function large_order_promo at 0x7f802c0a5430>]
>>>

6.2 Command mode

“ command ” Design patterns can also be simplified by passing functions as parameters .
We don't have to provide one for the caller Command example , But give it a function . here , The caller does not need to call command.execute(), Call directly command() that will do .MacroCommand It can be implemented as a definition __call__ Class of method . such ,MacroCommand An example of is the callable object that maintains a function list .
Example 6-9 MacroCommand Each instance of is internally storing a list of commands

class MacroCommand:
""" A command that executes a set of commands """
def __init__(self, commands):
self.commands = list(commands) #1
def __call__(self):
for command in self.commands: #2
command()

#1 Use commands Parameters to build a list , This ensures that the parameter is an iteratable object , Can also be in various MacroCommand Save a copy of each command reference in the instance
#2 call MacroCommand When an instance ,self.commands The commands in are executed in sequence


  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式師世界 All Rights Reserved