第六章:使用一等函数实现设计模式

6.1 案例分析:重构“策略”模式

如果合理利用作为一等对象的函数,某些设计模式可以简化,“策略”模式就是一个很好的例子。

6.1.1 经典的“策略”模式

如图中UML类图指出了“策略”模式对类的编排

1

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

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

电商领域有个功能明显可以使用“策略”模式,即根据客户的属性或订单中的商品计算折扣。
假如一个网店制定了下述折扣规则。

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

简单起见,我们假定一个订单一次只能享用一个折扣。
“策略”模式的 UML 类图见图 ,其中涉及下列内容。

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

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

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

实现Order类,支持插入式折扣策略

from abc import ABC, abstractmethod
from collections import nametuples

Customer = namedtuple('Customer' , 'name fidelity')

class LineItem:
    def __init__(self , product , quantily , 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 itme 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() * 0.5 if order.customer.fidelity >= 1000 else 0 

class BulkItemPromo(Promotion):  #第二个具体策略
    """单个商品为20个或以上时提供10%折扣""" 

    def discount(self , order):
        discount = 0 
        for item in order.cart:
            if itme.quantity >= 20:
                discount +=  item.total() * .1
        return discount


class LargeOrderPromo(Promotion): #第三个具体策略
    """订单中的不同商品达到10个或以上时提供7%折扣"""
    
    def discount(self , order):
        distinct_items = {itme.product for item in order.cart}
        if len(distinct_items) >= 10:
            return order.total() * .07
        return 0 

下面的示例是一些doctest , 在某个实现了上述规则的模块中演示和验证相关操作

joe  = Customer('John Doe' , 0)
ann = Customer('Ann Smith' , 11000)
cart = [LineItem('banana' , 4 , .5) , 
           LineItem('apple' , 10 , 1.5) , 
           LineItem('watermellon' , 5 , 5.0)]

Order(joe ,  cart , FidelityPromo())
Order(ann , cart , FidelityPromo())
banana_cart = [LineItem('banana' , 30 , .5),
                         LineItem('apple' , 10 , 1.5)]
Order(joe , banana_cart , BulkItemPromo())

long_order = [LineItem(str(item_code), 1, 1.0)
                      for item_code in range(10)]  

Order(joe , long_order , LargeOrderPromo())
Order(joe , cart , LargeOrderPromo())

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

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 = 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 += itme.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

6.2 “命令”模式

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

2

这个模式的做法是,在二者之间放一个 Command 对象,让它实现只有一个方法(execute) 的接口,调用接收者中的方法执行所需的操作。这样,调用者无需了解接收者的接口而且不同的接收者可以适应不同的 Command 子类。调用者有一个具体的命令,通过调用 execute 方法执行。
Gamma 等人说过:“命令模式是回调机制的面向对象替代品。”问题是,我们需要回调机 制的面向对象替代品吗?有时确实需要,但并非始终需要。
我们可以不为调用者提供一个 Command 实例,而是给它一个函数。此时,调用者不用调用 command.execute(),直接调用 command() 即可。MacroCommand 可以实现成定义了 call 方法的类。这样,MacroCommand 的实例就是可调用对象,各自维护着一个函数列表,供以后调用

class MacroCommand:
    """一个执行一组命令的命令"""
    
    def __init__(self. , commands):
        self.commands = list(commands)

    def __call__(self):
        for command in self.commands:
            command()

使用commands 参数构建一个列表,这样能确保参数是可迭代对象,还能在各个 MacroCommand 实例中保存各个命令引用的副本。

调用 MacroCommand 实例时,self.commands 中的各个命令依序执行。

复杂的“命令”模式(如支持撤销操作)可能需要更多,而不仅是简单的回调函数。即便 如此,也可以考虑使用 Python 提供的几个替代品。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容