你是否曾经打开过食品配送应用,在“最快路线”、“最便宜选项”或“停站次数最少”之间进行选择?又或者在结账时选择信用卡、PayPal或钱包余额等支付方式?实际上,在这两种情况下,很可能会用到策略模式

策略模式允许你定义一组算法,将每种算法放在不同的类中,并让它们在运行时可以互相替换。每当需要改变程序的行为时,你不必每次都编写冗长的if/elif代码链,只需更换相应的策略即可实现目标。

在本教程中,你将了解什么是策略模式、它为什么有用,以及如何通过实际例子在Python中实现它。

你可以在GitHub上获取相关代码

先决条件

在开始之前,请确保你已经满足以下要求:

  • 已安装Python 3.10或更高版本

  • 对Python类和方法有基本了解

  • 熟悉面向对象编程的概念

那我们开始吧!

目录

什么是策略模式?

策略模式是一种将一组相关的算法封装起来、使它们能够互相替换的设计模式。使用这些算法的对象被称为上下文,它无需了解具体算法的实现细节,只需将任务委托给当前被选中的策略即可。

想象一下GPS应用:目标地点是固定的,但你可以选择“避开高速公路”、“走最短路线”或“选择交通最少的路线”,而这些选项本身并不影响最终的目的地或应用程序的结构。每种路线选择方式其实就是一种不同的策略。

当遇到以下情况时,策略模式会非常有用:

  • 当你需要实现算法的多种变体时

  • 当你希望避免基于类型编写冗长的if/elif条件语句时

  • 当你希望在运行时改变程序的行为,而不修改上下文类时

  • 当应用程序的不同部分需要使用同一操作的多种变体时

现在让我们通过一些例子来进一步理解这个概念。

简单的策略模式示例

我们来构建一个简单的电子商务订单系统,在结账时可以应用不同的折扣策略。

首先,我们创建三种折扣策略:

class RegularDiscount:
    def apply(self, price):
        return price * 0.95  # 享受5%的折扣

class SeasonalDiscount:
    def apply(self, price):
        return price * 0.80  # 享受20%的折扣

class NoDiscount:
    def apply(self, price):
        return price  # 不进行任何折扣处理

每个类都包含一个名为apply的方法,该方法接受一个价格参数,并返回折扣后的价格。这些方法遵循相同的接口,但实现了不同的逻辑——这就是策略模式中的关键概念。

现在让我们创建一个Order类,让它使用其中某种折扣策略:

class Order:
    def __init__(self, product, price, discount_strategy):
        self.product = product
        self.price = price
        self.discount_strategy = discount_strategy

    def final_price(self):
        return self.discount_strategy.apply(self.price)

    def summary(self):
        print(f"商品名称 : {self.product}")
        print(f>原价 : ${self.price:.2f}")
        print(f>折扣后价格 : ${self.final_price():.2f}")
        print("-" * 30)

Order类就充当了我们的上下文。它本身并不包含任何折扣计算逻辑——所有这些功能都由discount_strategy.apply()方法来完成。无论你传递哪个策略对象,都会被用来执行相应的折扣计算。

现在我们来下达一些订单:

order1 = Order("机械键盘", 120.00, NoDiscount())
order2 = Order("笔记本电脑支架", 45.00, RegularDiscount())
order3 = Order("USB-C集线器", 35.00, SeasonalDiscount())

order1.summary()
order2.summary()
order3.summary()

运行上述代码后,你将会得到如下输出:

商品名称 : 机械键盘
原价 : $120.00
折扣后价格 : $120.00
------------------------------
商品名称 : 笔记本电脑支架
原价 : $45.00
折扣后价格 : $42.75
------------------------------
商品名称 : USB-C集线器
原价 : $35.00
折扣后价格 : $28.00
------------------------------

请注意,Order类从未执行过if discount_type == "seasonal"这样的判断。它只是简单地调用apply()方法,然后将折扣计算的任务交给相应的策略对象来处理。今后如果需要添加新的折扣类型,只需要创建一个新的类即可,其他代码无需做任何修改。

在运行时切换策略

策略模式的最大优势之一就是:你可以在程序运行过程中随时更改所使用的策略。比如,假设用户在购物过程中升级为了高级会员:

class ShoppingCart:
    def __init__(self):
        self.items = []
        self.discount_strategy = NoDiscount()  # 默认策略

    def add_item(self, name, price):
        self.items.append({"name": name, "price": price})

    def set_discount(self, strategy):
        self.discount_strategy = strategy
        print(f>折扣策略已更改为:{strategy.__class__.__name__}")

    def checkout(self):
        print("\n--- 结账摘要 ---")
        total = 0
        for item in self.items:
            discounted = self.discount_strategy.apply(item["price"])
            print(f"{item['name']}: ${discounted:.2f}")
            total += discounted
        print(f>总金额 : ${total:.2f}\n")

set_discount方法允许我们在任何时候更改所使用的折扣策略。让我们来看一下这个方法的实际应用效果:

cart = ShoppingCart()
cart.add_item("Notebook", 15.00)
cart.add_item("Desk Lamp", 40.00)
cart.add_item("Monitor Riser", 25.00)

# 以普通顾客的身份进行结账
cart.checkout()

# 用户升级为季节性促销会员
cart.set_discount(SeasonalDiscount())
cart.checkout()

运行上述代码后,将会得到如下输出:

--- 结账摘要 ---
笔记本:15.00美元
台灯:40.00美元
显示器支架:25.00美元
总金额:80.00美元

折扣类型已更改为:季节性促销折扣

--- 结账摘要 ---
笔记本:12.00美元
台灯:32.00美元
显示器支架:20.00美元
总金额:64.00美元

实际上,购物车本身并没有发生任何变化,只有所使用的折扣策略发生了改变。这就是将“行为逻辑”与使用该逻辑的“上下文环境”分开处理的优点所在。

使用抽象基类

到目前为止,还没有任何规定要求所有的折扣策略都必须具备apply方法。如果有人创建了一个折扣策略却忘记了实现这个方法,那么在运行时他们就会遇到AttributeError错误。我们可以利用Python的抽象基类来避免这种情况。

from abc import ABC, abstractmethod

class DiscountStrategy(ABC):
    @abstractmethod
    def apply(self, price: float) -> float:
        pass

现在,让我们将所有的折扣策略都改写为继承自类:

class RegularDiscount(DiscountStrategy):
    def apply(self, price):
        return price * 0.95

class SeasonalDiscount(DiscountStrategy):
    def apply(self, price):
        return price * 0.80

class NoDiscount(DiscountStrategy):
    def apply(self, price):
        return price

如果有人尝试创建一个没有实现apply方法的折扣策略,Python会在他们尝试实例化这个类时立即抛出TypeError错误——甚至在任何代码开始执行之前就会发生这种错误。这样的错误处理方式要清晰得多。

class BrokenStrategy(DiscountStrategy):
    pass  # 忘了实现apply()方法

s = BrokenStrategy()  # 这里会立即抛出TypeError错误

在大型团队或共享代码库中,使用抽象基类尤其有用。因为这样就可以明确规定:所有的折扣策略必须实现apply方法。否则,就会出现像上面示例中所示的错误。

      2     pass  # 忘了实现apply()方法
      3 
----> 4 s = BrokenStrategy()  // 这里会立即抛出TypeError错误

TypeError: 无法实例化抽象类BrokenStrategy,因为没有实现抽象方法'apply'

何时使用策略模式

在以下情况下,策略模式是非常适用的:

  • 当你需要根据不同的类型来执行不同的操作时——那些冗长的if/elif语句,用于判断某个“模式”或“类型”,这时策略模式就能派上用场。

  • 当行为需要在运行时进行动态调整时——比如用户设置或配置参数的变化能够导致算法的切换,而无需重新启动程序。

  • 当你正在构建可扩展的系统时——新的功能可以通过添加新的类来实现,而无需修改现有的代码。

  • 当你希望独立地测试各种算法时——每个策略都对应一个独立的类,这样进行单元测试就会变得非常方便。

在以下情况下应避免使用这种模式:

  • 当你只有两种永远不会发生变化的情况时,使用简单的 if/else 语句就完全足够了。

  • 当这些策略需要共享大量状态时,将它们分开成不同的类只会增加代码的复杂性,而并不会带来任何实际好处。

总结

希望这篇教程对您有所帮助。总之,策略模式为您提供了一种简洁的方法来管理不同的行为方式,同时避免了让类中充斥过多的条件逻辑。这样,代码的结构就能保持简单和稳定,而具体的处理逻辑则由相应的策略类来负责。

我们介绍了这种模式的基本原理、运行时策略的切换机制,以及如何利用抽象基类来确保各种策略之间的兼容性。与大多数设计模式一样,开始时应该从最简单的形式入手:即使不使用抽象基类,将算法分别放在不同的类中,也会使代码更易于阅读、测试和扩展。

祝您编程愉快!

Comments are closed.