Python设计模式:策略模式

发布于 2021-05-10  1,089 次阅读


策略模式(Strategy Pattern)隶属于设计模式中的行为型模式,是日常开发中使用最广的一个模式。

定义

策略模式定义了一系列的算法,并将每一个算法封装起来,而且使他们可以相互替换,让算法可独立于使用的客户而变化。

结构

  • Strategy:策略抽象接口
  • Context:策略上下文,负责与具体的策略类交互
  • ConcreteStrategy:具体的策略实现

先看一个经典例子

超市做活动的场景来举个例子。

  • Context:Order类,订单信息,包括商品,价格和数量,以为购买者等
  • Stragety:Promotion类,抽象基类,包含一个抽象方法(计算折扣)
  • ContreteStragety:分三个类,FidelityPromo,BulkItemPromo,LargeOrderPromo,实现具体的折扣计算方法。
# -*- coding: utf-8 -*-
# @Time    : 2021/5/10 下午2:10
# @Author  : chenshiyang
# @Email   : chenshiyang@blued.com
# @File    : Strategy_pattern.py
# @Software: PyCharm

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):
        pass


class BulkItemPromo(Promotion):
    '''
    如果单项商品购买10件,即可9折。
    '''
    def discount(self, order):
        discount = 0
        for item in order.cart:
            if item.quantity >= 10:
                discount += item.total() * 0.1
        return discount

class LargeOrderPromo(Promotion):
    '''
    如果订单总金额大于等于500,就可以立减50
    '''
    def discount(self, order):
        discount = 0
        if order.total() >= 500:
            discount = 50

        return discount

if __name__ == '__main__':
    xm = Customer('xiaoming', 1500)
    cart = [LineItem('鞋子', 200, 3), LineItem('衣服', 600, 1)]
    order = Order(xm, cart, BulkItemPromo())
    print(order)

但是问题很快又来了,商场搞活动,却让顾客手动选择使用哪个优惠策略,作为一个良心的商家,应该要能自动对比所有策略得出最优惠的价格来给到顾客。这就要求后台代码要能够找出当前可用的全部策略,并一一比对折扣

# 实现一个最优策略类找出最优的策略
class BestPromo(Promotion):
    def discount(self, order):

        all_promotion = [globals()[name] for name in globals() if
                         name.endswith('Promo') and name != 'BestPromo']

        # 计算最大折扣
        return max([promo().discount(order) for promo in all_promotion])

通过以上例子,可以总结出使用策略模式的好处

  1. 扩展性优秀,移植方便,使用灵活。可以很方便扩展策略;
  2. 各个策略可以自由切换。这也是依赖抽象类设计接口的好处之一;

但同时,策略模式 也会带来一些弊端。

  1. 项目比较庞大时,策略可能比较多,不便于维护;
  2. 策略的使用方必须知道有哪些策略,才能决定使用哪一个策略

具体的策略类用函数来实现,然后在实例化 Order 类时指定这个策略函数即可

def fidelity_promo(order):
    return order.total()*0.05 if order.customer.fidelity>=1000 else 0

def bulk_promo(order):
    discount=0
    for item in order.cart:
        if item.quantity>=20:
            discount+=item.total()*0.1
    return discount

使用场景

  1. 系统中有面对一个问题的多种解决方案
  2. 系统可以动态地选择解决方案,而无需变化代码
  3. 封装算法细节,实现对客户端使用方的隐藏

策略模式在日常中经常使用,有时可能我们都没有意识到,符合上面所说的三个条件的场景都可以使用策略模式,我们列一下日常具体的场景:

  1. 排序算法
  2. List、Map 的选择与实现
  3. 网站会员、等级、积分、折扣、支付方式
  4. 。。。

就在编程领域之外,有许多例子是关于策略模式的。例如:

如果我需要在早晨从家里出发去上班,我可以有几个策略考虑:我可以乘坐地铁,乘坐公交车,走路或其它的途径。每个策略可以得到相同的结果,但是使用了不同的资源。

实现方式

  1. 从上下文类中找出修改频率较高的算法 (也可能是用于在运行时选择某个算法变体的复杂条件运算符)。
  2. 声明该算法所有变体的通用策略接口。
  3. 将算法逐一抽取到各自的类中, 它们都必须实现策略接口。
  4. 在上下文类中添加一个成员变量用于保存对于策略对象的引用。 然后提供设置器以修改该成员变量。 上下文仅可通过策略接口同策略对象进行交互, 如有需要还可定义一个接口来让策略访问其数据。
  5. 客户端必须将上下文类与相应策略进行关联, 使上下文可以预期的方式完成其主要工作。

策略模式优缺点

优点

  • 你可以在运行时切换对象内的算法。
  • 你可以将算法的实现和使用算法的代码隔离开来。
  • 你可以使用组合来代替继承。
  • 开闭原则。 你无需对上下文进行修改就能够引入新的策略。
  • 使功能改变或者扩展更容易。具体地说,修改一个算法不必重新编译“Client”与“Context”类。增加一个新算法时,在应用程序暂时还不想使用该新算法的情况下,不必重新编译“Client”与“Context”类。

缺点

  • 如果你的算法极少发生改变, 那么没有任何理由引入新的类和接口。 使用该模式只会让程序过于复杂。
  • 客户端必须知晓策略间的不同——它需要选择合适的策略。
  • 许多现代编程语言支持函数类型功能, 允许你在一组匿名函数中实现不同版本的算法。 这样, 你使用这些函数的方式就和使用策略对象时完全相同, 无需借助额外的类和接口来保持代码简洁。

最后

策略模式运用好能去除代码中大量的if else,策略模式就是以面向对象的多态取代条件表达式。


一名测试工作者,专注接口测试、自动化测试、性能测试、Python技术。