zoukankan      html  css  js  c++  java
  • Design Patterns with First-Class Functions

    Although design patterns are language-independent, that does not mean every pattern applies to every language.   

    If we assumed procedural languages, we might have included design patterns called "Inheritance", "Encapsulation" and "Polymorphism". 

    In particular, in the context of languages with first-class functions, Norvig suggests rethinking the Strategy, Command, Template Method, and Visitor patterns. The general idea is: you can replace instances of some participant class in these patterns with simple functions, reducing a lot of boilerplate code.

    1. Case Study: Refactoring Strategy.

    The Strategy pattern is summarized like this in Design Patterns:

    # Define a family of algorithms, encapsulate each one, and make them interchangeable.  Strategy lets the algorithm vary independently from clients that use it.

    The participants of Strategy pattern are:

    (1).  Context; (2). Strategy ; (3). Concrete Strategy

    1.1 Classic Strategy

    Example 6-1. Implementation Order class with pluggable discount strategies.

    """
    1. Customers with 1000 or more fidelity points get a a global 5% discount per order.
    2. A 10% discount is applied to each line item with 20 or more units in the same order.
    3. Orders with at least 10 distinct items get a 7% global discount.
    """
    
    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:  # 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.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):  # the Strategy: an abstract base class
    
        @abstractmethod
        def discount(self, order):
            """Return discount as a positive dollar amount"""
    
    
    class FidelityPromo(Promotion):  # first Concrete Strategy
        """5% discount for customers with 1000 or more fidelity points"""
    
        def discount(self, order):
            return order.total() * .05 if order.customer.fidelity >= 1000 else 0
    
    
    class BulkItemPromo(Promotion):  # second Concrete Strategy
        """10% discount for each LineItem with 20 or more units"""
    
        def discount(self, order):
            discount = 0
            for item in order.cart:
                if item.quantity >= 20:
                    discount += item.total() * .1
            return discount
    
    
    class LargeOrderPromo(Promotion):
        """7% discount for orders with 10 or more distinct items"""
    
        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('watermelon', 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>
    """
    
    """
    This example 6-1 works perfectly well, but the same functionality can be implemented with less code in Python by using
    functions as objects.
    """

    1.2 Function-Oriented Strategy

    Each concrete strategy in Example 6-1 is a class with a single method, discount. Furthermore, the strategy instances have no state(no instance attributes). You could say they look a lot like plain functions, and you would be right. Example 6-3 is a refactoring of Example 6-1, replacing the concrete strategies with simple functions and removing the Promo abstract class.

    Example 6-3. Order class with discount strategies implemented as functions.

    """
    This example is a refactoring of Example 6-1, replacing the concrete strategies with simple functions and removing the
    Promo abstract class.
    """
    
    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)  # To compute a discount, just call the self.promotion() function.
            return self.total() - discount
    
        def __repr__(self):
            fmt = '<Order total: {:.2f} due: {:.2f}>'
            return fmt.format(self.total(), self.due())
    
    
    def fidelity_promo(order):  # Each strategy is a function.
        """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
    
    
    # 运行结果:
    """
    >>> joe = Customer('John Doe', 0)
    >>> ann = Customer('Ann Smith', 1100)
    >>> cart = [LineItem('banana', 4, .5),
    ...         LineItem('apple', 10, 1.5),
    ...         LineItem('watermelon', 5, 5.0)]
    >>> 
    >>> Order(joe, cart, fidelity_promo)
    <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>
    """

    1.3 Choosing the Best Strategy: Simple Approach

    Example 6-6. best_promo finds the maximum discount iterating over a list of functions.

    from ex6_3_order_class_with_discount_strategies_as_functions import *
    
    promos = [fidelity_promo, bulk_item_promo, large_order_promo]  # promos: list of the strategies implemented as functions
    
    
    def best_promo(order):  # best_promo takes an instance of Order as argument, as do the *_promo functions.
        """Select best discount available
        """
        return max(promo(order) for promo in promos)
        # Using a generator expression, we apply each of the functions from promos to the order,
        # and return the maximum discount computed
    
    
    # 运行结果:
    """
    >>> joe = Customer('John Doe', 0)
    >>> ann = Customer('Ann Smith', 1100)
    >>> cart = [LineItem('banana', 4, .5),
    ...         LineItem('apple', 10, 1.5),
    ...         LineItem('watermelon', 5, 5.0)]
    >>> long_order = [LineItem(str(item_code), 1, 1.0)
    ...               for item_code in range(10)]
    >>> banana_cart = [LineItem('banana', 30, .5),
    ...                LineItem('apple', 10, 1.5)]
    >>> 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>
    >>> Order(ann, cart, best_promo)
    <Order total: 42.00 due: 39.90>
    """

    Example 6-7. The promos list is built by introspection of the module global namespace.

    """
    This example is a somewhat hackish way of using globals to help best_promo automatically find the other available
    *_promo functions.
    """
    
    """
    Modules in Python are also first-class objects, and the standard library provides several functions to handle them.
    The built-in globals() is described as follows in the Python docs:
    globals()
        Return a dictionary representing the current global symbol table. This is always the dictionary of the current
        module(inside a function or method, this the module where it is defined,not the module from which it is called.)
    """
    
    from ex6_3_order_class_with_discount_strategies_as_functions import *
    
    
    promos = [globals()[name] for name in globals()     # Iterate over each name in the dictionary returned by globals()
              if name.endswith('_promo')    # Select only names that end with the _promo suffix
              and name != 'best_promo']     # Filter out best_promo itself, to avoid an infinite recursion.
    
    
    def best_promo(order):
        """Select best discount available
        """
        return max(promo(order) for promo in promos)

    注: Example 6-7 例子中我是用导入的方式把 *_promo 函数导入到了 best_promo函数所在的py文件中, 但书上的愿意是上述代码 和 *_promo 函数同属一个py文件中。

    Example 6-8. The promos list is built by introspection of a new promotions module.

    """
    Another way of collecting the available promotions would be to create a module and put all the strategies functions
    here, except for the best_promo
    """
    
    import inspect
    import promotions
    
    promos = [func for name, func  # built by introspection of a separate module called promotions.
              in inspect.getmembers(promotions, inspect.isfunction)]
    
    
    def best_promo(order):
        """Select best discount available
        """
        return max(promo(order) for promo in promos)
    
    
    print(promos)
    # 打印结果:
    # [<function bulk_item_promo at 0x1035efd08>, <function fidelity_promo at 0x1035efc80>, <function large_order_promo at 0x1035efd90>]
    
    """
    The function inspect.getmembers returns the attributes of an object -- in this case, the promotions module -- optionally
    filtered by a predicate(a boolean function). We use inspect.isfunction to get only the functions from teh module.
    """

    2. Command

    Example 6-9. Each instance of MacroCommand has an internal list of commands.

    class MarcoCommand:
        """A command that execute a list of commands"""
    
        def __init__(self, commands):
            self.commands = list(commands)
            # Building a list from the commands arguments ensures that is iterable and keeps a local copy of the command
            # references in each MacroCommand instance.
    
        def __call__(self):
            for command in self.commands:
                command()
    
    
    """
    The goal of Command is to decouple an object that invokes an operation (the Invoker) from the provider object that 
    implements it (the Receiver).
    The idea is to put a Command object between the two(Invoker and Receiver), implementing an interface with a single
    method, execute, which calls some method in the Receiver to perform the desired operation. That way the Invoker does 
    not need to know the interface of the Receiver, and different receivers can be adapted through different Command
    subclass. The Invoker is configured with a concrete command and calls its execute method to operate it.
    
    "Commands are an object-oriented replacement for callbacks."
    
    Instead of giving the Invoker a Command instance, we can simply give it a function. Instead of calling 
    command.execute(), the Invoker can just call command()
    """

    Sometimes you may encounter a design pattern or an  API that requires taht components implement an interface with a single method, and that method has a generic-sounding name such as "execute", "run" or "doIt". Such patterns or APIs often can be implemented with less boilerplate code in Python using first-class functions or other callables.

  • 相关阅读:
    游戏编程模式--原型模式
    游戏编程模式--观察者模式
    游戏编程模式--享元模式
    游戏编程模式--命令模式
    mybatis的线程安全
    开发遇到的问题
    spring的ThreadLocal解决线程安全
    i++
    jvm内存初步了解
    注解@RequestMapping,@RequestBody
  • 原文地址:https://www.cnblogs.com/neozheng/p/12250862.html
Copyright © 2011-2022 走看看