• 流畅的python——6 使用一等函数实现设计模式


    六、使用一等函数实现设计模式

    符合模式并不表示做得对。 ——Ralph Johnson

    ​ 经典的《设计模式:可复用面向对象软件的基础》的作者之一

    策略模式

    订单 order
    折扣策略,比如有三种

    Promotion是策略基类,下面三个是具体实现的折扣策略

    上下文

      把一些计算委托给实现不同算法的可互换组件,它提供服务。在这个电商示例中,上下文是 Order,它会根据不同的算法计算促销折扣。

    策略

      实现不同算法的组件共同的接口。在这个示例中,名为 Promotion 的抽象类扮演这个角色。

    具体策略

      “策略”的具体子类。fidelityPromo、BulkPromo 和 LargeOrderPromo 是这里实现的三个具体策略。

    按照《设计模式:可复用面向对象软件的基础》一书的说明,具体策略由上下文类的客户选择。在这个示例中,实例化订单之前,系统会以某种方式选择一种促销折扣策略,然后把它传给 Order 构造方法。具体怎么选择策略,不在这个模式的职责范围内。

    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: # 上下文
         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) : # 策略:抽象基类
         @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
        
    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
    

    使用函数实现 策略 模式

    之前的例子,每个类只实现了一个方法,而且没有对象属性,所以,可以替换成三个策略函数。不再使用类。

    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 = '<Order total: {:.2f} due: {:.2f}>'
         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
    

    值得注意的是,《设计模式:可复用面向对象软件的基础》一书的作者指出:“策略对象通常是很好的享元(flyweight)。”

    那本书的另一部分对“享元”下了定义:“享元是可共享的对象,可以同时在多个上下文中使用。”

    共享是推荐的做法,这样不必在每个新的上下文(这里是 Order 实例)中使用相同的策略时不断新建具体策略对象,从而减少消耗。因此,为了避免“策略”模式的一个缺点(运行时消耗),《设计模式:可复用面向对象软件的基础》的作者建议再使用另一个模式。但此时,代码行数和维护成本会不断攀升。

    在复杂的情况下,需要具体策略维护内部状态时,可能需要把“策略”和“享元”模式结合起来。但是,具体策略一般没有内部状态,只是处理上下文中的数据。此时,一定要使用普通的函数,别去编写只有一个方法的类,再去实现另一个类声明的单函数接口。函数比用户定义的类的实例轻量,而且无需使用“享元”模式,因为各个策略函数在 Python 编译模块时只会创建一次。普通的函数也是“可共享的对象,可以同时在多个上下文中使用”。

    选择最佳策略:简单的方式

    best_promo 函数的实现特别简单,如示例 6-6 所示。

    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 = [globals()[name] for name in globals()
              if name.endswith('_promo')  # 取 _promo 结尾的名称
              and name != 'best_promo']  # 防止无限递归
    def best_promo(order):
        """选择可用的最佳折扣
         """
        return max(promo(order) for promo in promos)
    

    inspect.getmembers

    收集所有可用促销的另一种方法是,在一个单独的模块 promotions 中保存所有策略函数,把best_promo 排除在外。

    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)
    

    inspect.getmembers 函数用于获取对象(这里是 promotions 模块)的属性,第二个参数是可选的判断条件(一个布尔值函数)。我们使用的是 inspect.isfunction,只获取模块中的函数。

    不管怎么命名策略函数,示例都可用;唯一重要的是,promotions 模块只能包含计算订单折扣的函数。当然,这是对代码的隐性假设。如果有人在 promotions 模块中使用不同的签名定义函数,那么 best_promo 函数尝试将其应用到订单上时会出错。

    命令模式

    “命令”设计模式也可以通过把函数作为参数传递而简化。

    菜单驱动的文本编辑器的 UML 类图,使用命令设计模式实现。各个命令可以有不同的接收者(实现操作的对象)。对 PasteCommand 来说,接收者是Document。对 OpenCommand 来说,接收者是应用程序

    “命令”模式的目的是解耦调用操作的对象(调用者)和提供实现的对象(接收者)。

    这个模式的做法是,在二者之间放一个 Command 对象,让它实现只有一个方法(execute)的接口,调用接收者中的方法执行所需的操作。这样,调用者无需了解接收者的接口,而且不同的接收者可以适应不同的Command 子类。调用者有一个具体的命令,通过调用 execute 方法执行。注意,图中的 MacroCommand 可能保存一系列命令,它的 execute() 方法会在各个命令上调用相同的方法。

    Gamma 等人说过:“命令模式是回调机制的面向对象替代品。”问题是,我们需要回调机制的面向对象替代品吗?有时确实需要,但并非始终需要。

    我们可以不为调用者提供一个 Command 实例,而是给它一个函数。此时,调用者不用调用 command.execute(),直接调用 command() 即可。MacroCommand 可以实现成定义了__call__ 方法的类。这样,MacroCommand 的实例就是可调用对象,各自维护着一个函数列表,供以后调用。

    class MacroCommand:
        """一个执行一组命令的命令"""
        def __init__(self, commands):
            self.commands = list(commands)  # 使用 commands 参数构建一个列表,这样能确保参数是可迭代对象,还能在各个 MacroCommand 实例中保存各个命令引用的副本。
        def __call__(self):
            for command in self.commands:
                command()
    

    可以使用闭包在调用之间保存函数的内部状态。

    把实现单方法接口的类的实例替换成可调用对象。毕竟,每个Python 可调用对象都实现了单方法接口,这个方法就是 __call__

  • 相关阅读:
    JMeter做接口测试几个简单实例
    1
    Ubuntu基本命令学习
    commons-codec对字符串进行编码解码
    Python入门学习-DAY15-模块
    Python入门学习-DAY14-生成器,生成器表达式,内置函数,面向过程编程
    Python入门学习-DAY13-迭代器
    Python入门学习-DAY12-递归、三元表达式、列表生成式、字典生成式、匿名函数
    Python入门学习-DAY11-装饰器
    DAY10-Python入门学习-函数的对象与嵌套、名称空间与作用域、闭包函数
  • 原文地址:https://www.cnblogs.com/pythonwl/p/15344590.html
Copyright © 2020-2023  润新知