策略模式(Python一等函数实现)

"一等函数"这个词其实有点坑,听起来有点像特指某一部分函数,但是其实所有函数都是一等函数。

编程语言理论家把“一等对象”定义为满足下述条件的程序实体:

  • 在运行时创建
  • 能赋值给变量或数据结构的元素
  • 能作为参数传给函数
  • 能作为函数的返回结果

---《流畅的Python》第5章 一等函数

所以可以看出,所有函数都是一等对象,所以叫做“一等函数”。

策略模式解决的问题 

大多数时候我们面对的问题都有许多种处理方法。比如我上周去上海出差,我可以坐飞机去,可以坐动车去,也可以开车去,每种方法有各自的特点。在这种场景下,怎么到上海就是面对的问题,不同的交通工具就是处理办法,专业一点的说法就叫策略。策略模式就是解决这种根据不同条件,选择不同策略的机制。

写在策略前面

设计模式的本质是在已有的方案之上发现更好的方案,而不是全新的发明

这句话我想经常在脑子里过一下,提醒我在新写一个功能时,不要一上来就急着用设计模式。策略模式解决的问题场景用普通逻辑来实现大致是这样

def 去上海():
    if "赶时间并且去机场方便":
        做飞机去
        ...
    elif "只有火车票票才能报销":
        坐火车去
        ...
    elif "出差顺便自驾游":
        开车去
        ...
    else:
        暂时不支持的策略

策略模式的结构

策略模式由三个部分组成:

  • 上下文:把一些计算委托给实现不同算法的可互换组件(具体策略),它提供服务
  • 策略:实现不同算法的组件共同的接口
  • 具体策略:策略的子类,策略的接口的具体实现

类实现策略模式

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 = 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


if __name__ == '__main__':
    joe = Customer('John Doe', 0)
    ann = Customer("Ann Smith", 1100)
    cart = [LineItem("banana", 4, .5),
            LineItem("apple", 10, 1.5),
            LineItem("watermellon", 5, 5.0)]
    print(Order(joe, cart, FidelityPromo()))
    print(Order(ann, cart, FidelityPromo()))

    banana_cart = [LineItem("banana", 30, .5),
                   LineItem("apple", 10, 1.5)]
    print(Order(joe, banana_cart, BulkItemPromo()))

    long_cart = [LineItem(str(item_code), 1, 1.0) for item_code in range(10)]
    print(Order(joe, long_cart, LargeOrderPromo()))
    print(Order(joe, cart, LargeOrderPromo()))

比较不同实现:函数&类

如果具体策略比较复杂,那么使用类来实现并没有什么问题。但是假如具体策略只有一个方法,那么采用函数来实现也是一个不错的选择

使用函数实现策略模式的优点是:

  • 不用再为具体策略创建抽象基类
  • 不用在运行时创建具体策略类的实例对象
  • 函数对象比自定义的类的实例对象更轻量
  • 不需要使用享元模式,因为函数本身就是享元

具体实现

场景:Customer表示客户,LineItem类表示购物车内的产品,Order表示客户的订单,*_promo表示具体策略,用来计算不同场景下的折扣,best_promo用来自动选择折扣力度最大的具体策略。

# 策略模式(函数实现)

import inspect
import promotions
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 += 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


# promos = [fidelity_promo, bulk_item_promo, large_order_promo]
# promos = [globals()[name] for name in globals() if name.endswith("_promo")]
promos = [func for name, func in inspect.getmembers(promotions, inspect.isfunction)]


def best_promo(order):
    """ 选择可用的最佳折扣 """
    return max(promo(order) for promo in promos)


if __name__ == '__main__':
    joe = Customer('John Doe', 0)
    ann = Customer("Ann Smith", 1100)
    cart = [LineItem("banana", 4, .5),
            LineItem("apple", 10, 1.5),
            LineItem("watermellon", 5, 5.0)]
    print(Order(joe, cart, fidelity_promo))
    print(Order(ann, cart, fidelity_promo))

    banana_cart = [LineItem("banana", 30, .5),
                   LineItem("apple", 10, 1.5)]
    print(Order(joe, banana_cart, bulk_item_promo))

    long_cart = [LineItem(str(item_code), 1, 1.0) for item_code in range(10)]
    print(Order(joe, long_cart, large_order_promo))
    print(Order(joe, cart, large_order_promo))

    print(Order(joe, long_cart, best_promo))
    print(Order(joe, banana_cart, best_promo))
    print(Order(ann, cart, best_promo))

再定义promotions.py,其中的具体策略和上面是一样的,这里表示的是一种自动获取具体策略的方法

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值