策略模式练习——支付、折扣与路线策略
按《Python 设计模式——策略模式》的建议,通过三道练习巩固:① 支付方式策略;② 折扣策略;③ 路线计算策略。每步都有完整可运行代码和验证要点。
练习一:支付方式策略
目的
体会上下文持有策略、执行时委托给策略;换支付方式只换策略对象,不写 if 支付方式==xxx。
要求
- 定义 PayStrategy 接口,有方法 pay(amount: float) -> bool。
- 实现三个具体策略:AlipayStrategy、WechatPayStrategy、BankCardStrategy,pay 时分别打印“[支付宝] 支付 xx 元”等并返回 True。
- 实现 Order(上下文):有 set_pay_strategy(strategy)、do_pay(amount);do_pay 内部只调 self._pay_strategy.pay(amount),不写 if 判断是哪种支付方式。
- 客户代码:创建 Order,先设为支付宝策略并 do_pay(100),再设为微信策略并 do_pay(50),验证两次输出正确。
参考答案
from abc import ABC, abstractmethod
class PayStrategy(ABC):
@abstractmethod
def pay(self, amount: float) -> bool:
pass
class AlipayStrategy(PayStrategy):
def pay(self, amount: float) -> bool:
print(f" [支付宝] 支付 {amount} 元")
return True
class WechatPayStrategy(PayStrategy):
def pay(self, amount: float) -> bool:
print(f" [微信] 支付 {amount} 元")
return True
class BankCardStrategy(PayStrategy):
def pay(self, amount: float) -> bool:
print(f" [银行卡] 支付 {amount} 元")
return True
class Order:
def __init__(self):
self._pay_strategy = None
def set_pay_strategy(self, strategy: PayStrategy):
self._pay_strategy = strategy
def do_pay(self, amount: float) -> bool:
if not self._pay_strategy:
print(" 未选择支付方式")
return False
return self._pay_strategy.pay(amount)
# 使用
order = Order()
order.set_pay_strategy(AlipayStrategy())
order.do_pay(100) # [支付宝] 支付 100 元
order.set_pay_strategy(WechatPayStrategy())
order.do_pay(50) # [微信] 支付 50 元
验证要点
- 第一次 do_pay 输出 [支付宝] 支付 100 元,第二次输出 [微信] 支付 50 元。
- 确认:Order.do_pay 内部没有 if 判断支付方式类型,只有 return self._pay_strategy.pay(amount)。
练习二:折扣策略
目的
“计算折后价”有多种算法(无折扣、VIP 九折、SVIP 八折);每种算法一个策略,使用方只依赖策略接口,根据用户等级注入不同策略。
要求
- 定义 DiscountStrategy 接口,有方法 discount(amount: float) -> float(返回折后金额)。
- 实现 NoDiscount(原价)、VIPDiscount(九折)、SVIPDiscount(八折)。
- 实现 PriceCalculator(上下文):构造或 set_strategy 注入策略;final_price(amount) 内部只调 self._strategy.discount(amount)。
- 客户代码:用 VIP 策略算 100 元得 90;换成 SVIP 策略算 100 元得 80。验证结果正确。
参考答案
from abc import ABC, abstractmethod
class DiscountStrategy(ABC):
@abstractmethod
def discount(self, amount: float) -> float:
pass
class NoDiscount(DiscountStrategy):
def discount(self, amount: float) -> float:
return amount
class VIPDiscount(DiscountStrategy):
def discount(self, amount: float) -> float:
return amount * 0.9
class SVIPDiscount(DiscountStrategy):
def discount(self, amount: float) -> float:
return amount * 0.8
class PriceCalculator:
def __init__(self, strategy: DiscountStrategy = None):
self._strategy = strategy or NoDiscount()
def set_strategy(self, strategy: DiscountStrategy):
self._strategy = strategy
def final_price(self, amount: float) -> float:
return self._strategy.discount(amount)
# 使用
calc = PriceCalculator(VIPDiscount())
print(calc.final_price(100)) # 90.0
calc.set_strategy(SVIPDiscount())
print(calc.final_price(100)) # 80.0
验证要点
- VIP 策略下 final_price(100) 为 90.0,SVIP 策略下为 80.0。
- PriceCalculator.final_price 只委托给 self._strategy.discount(amount),不写 if 用户等级。
练习三:路线计算策略
目的
“计算从 A 到 B 的路线/方式”有多种做法(驾车、步行、公交);每种做法一个策略,导航类持有策略,算路线时委托给策略。
要求
- 定义 RouteStrategy 接口,有方法 calculate(from_place: str, to_place: str) -> str,返回一段描述路线的字符串。
- 实现 CarRoute、WalkRoute、TransitRoute(公交),calculate 时分别返回类似“驾车: A -> B, 约 20 分钟”的字符串。
- 实现 Navigator(上下文):set_strategy(strategy)、route(from_place, to_place);route 内部只调 self._strategy.calculate(from_place, to_place) 并返回。
- 客户代码:先设驾车策略调用 route(“家”, “公司”),再设公交策略再调一次,验证两段输出不同且都包含“家”“公司”。
参考答案
from abc import ABC, abstractmethod
class RouteStrategy(ABC):
@abstractmethod
def calculate(self, from_place: str, to_place: str) -> str:
pass
class CarRoute(RouteStrategy):
def calculate(self, from_place: str, to_place: str) -> str:
return f" 驾车: {from_place} -> {to_place}, 约 20 分钟"
class WalkRoute(RouteStrategy):
def calculate(self, from_place: str, to_place: str) -> str:
return f" 步行: {from_place} -> {to_place}, 约 50 分钟"
class TransitRoute(RouteStrategy):
def calculate(self, from_place: str, to_place: str) -> str:
return f" 公交: {from_place} -> {to_place}, 约 35 分钟"
class Navigator:
def __init__(self, strategy: RouteStrategy = None):
self._strategy = strategy
def set_strategy(self, strategy: RouteStrategy):
self._strategy = strategy
def route(self, from_place: str, to_place: str) -> str:
if not self._strategy:
return " 请先选择出行方式"
return self._strategy.calculate(from_place, to_place)
# 使用
nav = Navigator(CarRoute())
print(nav.route("家", "公司")) # 驾车: 家 -> 公司, 约 20 分钟
nav.set_strategy(TransitRoute())
print(nav.route("家", "公司")) # 公交: 家 -> 公司, 约 35 分钟
验证要点
- 驾车策略输出含 “驾车” 和 “家”“公司”,公交策略输出含 “公交” 和 “家”“公司”。
- Navigator.route 只调 self._strategy.calculate(…),不写 if 出行方式。
三步汇总与自检
| 练习 | 重点 | 关键点 |
|---|---|---|
| 一 | 支付策略 | 上下文 set_pay_strategy + do_pay 委托;换策略即换支付方式,无 if。 |
| 二 | 折扣策略 | 上下文 set_strategy + final_price 委托;不同策略返回不同折后价。 |
| 三 | 路线策略 | 上下文 set_strategy + route 委托;不同策略返回不同路线描述。 |
自检问题
-
策略模式里,上下文为什么要持有“策略接口”而不是具体类?
这样上下文只依赖抽象,可以注入任意实现了接口的具体策略,换策略不用改上下文代码,符合依赖倒置和开闭原则。 -
策略和状态有什么区别?
策略一般由客户/上下文在外部选择并注入,策略类通常不会主动把上下文改成另一种策略;状态会在处理请求时主动调用 context.set_state(下一状态),由状态驱动转移。 -
若要新增一种支付方式(如“云闪付”),需要改哪些地方?
只需新增一个策略类(如 CloudPayStrategy)实现 PayStrategy,在需要的地方 order.set_pay_strategy(CloudPayStrategy());Order 和原有策略类都不用改。
做完以上三道练习,再对照《策略模式》文档中的示例,对策略模式会掌握得比较扎实。建议每道题先自己写一遍,再对照参考答案和验证要点检查。