zoukankan      html  css  js  c++  java
  • 流畅的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__

  • 相关阅读:
    入门菜鸟
    FZU 1202
    XMU 1246
    Codeforces 294E Shaass the Great 树形dp
    Codeforces 773D Perishable Roads 最短路 (看题解)
    Codeforces 814E An unavoidable detour for home dp
    Codeforces 567E President and Roads 最短路 + tarjan求桥
    Codeforces 567F Mausoleum dp
    Codeforces 908G New Year and Original Order 数位dp
    Codeforces 813D Two Melodies dp
  • 原文地址:https://www.cnblogs.com/pythonwl/p/15344590.html
Copyright © 2011-2022 走看看