• 《Fluent Python》- 06 使用一等函数实现设计模式


    这节主要说明使用Python的一等函数实现一些设计模式(主要是策略模式)

    经典的“策略”模式

    《设计模式:可复用面向对象软件的基础》一书是这样概述“策略”模式的:

    定义一些列算法,把它们一一封装起来,并且使它们可以相互替换。本模式使得算法可以独立于使用它的客户而变化。

    接下来用电商中的“折扣”来简单模拟一下,折扣规则如下:

    • 有1000或者以上积分的顾客,每个订单享5%的折扣。
    • 同一订单中,单个商品的数量达到20个或以上,享10%折扣 。
    • 订单中的不同商品达10个以上,享7%折扣。

    “策略”模式里,涉及以下内容:

    上下文:

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

    策略:

        实现不同算法的组件公共接口。这里示例里是Promotion。

    具体策略:

        “策略”的具体子类。示例就是图中下面那三个

    代码实现:

    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 = 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 >= 0:
                    discount += item.total() * .1
            return discount
    
    class LargeOrderPromo(Promotion): # 第三个
        def discount(self, order):
            distinct_items = {item.product for item in order.cart}
            if len(distinct_items) >= 10:
                return order.total() * .07
            return 0

    我们这里把Promotion定义为抽象基类(ABC),主要是为了使用@abstractmethod装饰器。具体演示就不演示了,这里我们采用的是将Python视为对象的方式,下面展示去使用更少代码的方式。

    使用函数实现“策略”模式

    在上面的那个例子里,我们每个具体策略都是一个类,而且都定义了一个方法discount。此外我们的策略是没有转态的,没有属性。下面采用基于函数的方式实现:

    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 = 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 >= 0:
                discount += item.total() * .1
        return discount
    
    def large_order_promo(order): # 第三个
        distinct_items = {item.product for item in order.cart}
        if len(distinct_items) >= 10:
            return order.total() * .07
        return 0

    与使用对象看上去大同小异,我们在使用的时候就只需要传函数而没必要在去实例化新促销对象。

    在《设计模式:可复用面向对象软件的基础》中,作者说到:策略对象通常是很好的享元(享元就是可共享对象,可以在多个上下文中使用),这样就不必要在使用相同策略时反复创建对象。所以作者建议再使用另一个模式。但这样代码的维护成本会不断上升。在复杂的情况下,需要具体策略维护内部转态时,可能需要把“策略”和“享元”模式结合,但是具体策略通常是没有内部状态的,只是处理上下文数据。此时,一定要使用普通的函数,别去编写只有一个方法的类,再去实现一个另一个类声明的单函数接口。在Python中,普通函数,也是“可共享对象,可以同时在多个上下文中使用”。

    选择策略

    我们需要一个方法帮助顾客选取最佳的策略:

    promos = [fidelity_promo, bulk_item_promo, large_order_promo]
    
    def best_promo(order):
        return max(promo(order) for promo in promos)

    上面代码易于阅读,但是维护就得注意,因为如果新增加了具体策略,promos就一定得手动添加。否则取最大时是不会考虑的。

    那么我们就需要解决这个问题,需要想办法找出模块中的所有策略:

    promos = [globals()[name] for name in globals()
               if name.endswith('_promo') and name != 'best_promo'] # 推导方式,不用手动输入

    我们去寻找'_promo'结尾的函数,这样我们就必须规范命名规则。

    当然,我们也可以简单的采用高阶内省函数:

    promos = [func for name, func in 
                  inspect.getmembers(promotions, inspect.isfunction())]
        # 注意一下,inspect模块是自己导入的。promotions模块就是你定义的存放具体策略的模块

    inspect.getmembers 是获取对象的属性(这里就是promotions),第二个参数是可选判断条件。我们这个参数只是获取模块中的函数,这里我们就可以自由命名了,要注意的是,promotions里面只能放具体策略。

    最后还有一个命令模式,我这里就不赘述了,和策略模式类似,核心思想还是把类换成函数。Python提供了一个很好的函数就是__call__。

  • 相关阅读:
    Python读写Excel文件和正则表达式
    R Language Learn Notes
    Electron小记
    Unity商店下载的文件保存路径?
    Unity LineRenderer制作画版
    unity图形圆形展开
    [转]资深CTO:关于技术团队打造与管理的10问10答
    unity游戏在ios11上不显示泰语解决办法
    jQuery火箭图标返回顶部代码
    jQuery火箭图标返回顶部代码
  • 原文地址:https://www.cnblogs.com/Moriarty-cx/p/12787564.html
Copyright © 2020-2023  润新知