策略模式精讲+实战

策略模式(Strategy Pattern)精讲 —— 结合 Spring 实战

1. 官方标准定义(GoF 原话)

定义一组算法,将每个算法都封装起来,并且使它们之间可以互相替换。该模式让算法的变化独立于使用算法的客户端(调用方)。核心原则:针对接口编程,而不是针对实现编程。策略模式(Strategy Pattern)是 GoF 23 种设计模式中的行为型模式。

2. 一句话"人话"解读

它是一个策略路由器。你只需要告诉上下文(Context)你想要什么"策略标识"(比如"ALIPAY"),它就会自动把对应的"技能/算法"(比如支付宝支付逻辑)拿出来执行,而调用方完全不用关心底层是怎么if-else判断类型的。

3. 模式的核心三要素(结构定义)

为满足上面的定义,模式必须包含以下 3 个角色:

角色职责对应代码
抽象策略(Strategy)定义一个公共接口,所有算法都遵循这个"合同"PaymentStrategy接口
具体策略(ConcreteStrategy)实现接口的具体类,封装了具体的业务算法AlipayStrategyWechatStrategy
上下文(Context)持有策略引用,负责根据条件路由到具体策略PaymentContext
设计原则
  • 封装变化:找出应用中可能需要变化的部分,把它们独立出来
  • 面向接口编程:策略的具体实现依赖接口,而不是依赖实现类
  • 组合优于继承:Context 通过组合持有一个 Strategy 引用,而不是继承具体策略
4. 为什么这个定义很重要?(结合 Spring 场景)
  • "互相替换":因为 Spring 把策略类注入了Map<String, Strategy>,只要type匹配,随时可以替换某个策略的实现类,调用方完全无感知。新增策略时,客户端代码完全不用改,完美符合"开闭原则"。
  • "变化独立于客户端":客户端(Controller)只需要传"WECHAT",不需要知道微信支付类是否被 AOP 代理了。无论注入的是原始类还是代理类,代码都能正常运行——这就是接口编程的威力。
5. 代码实战(解决支付场景)

假设有一个支付系统,支持支付宝、微信等多种渠道。我们将使用 Spring 的依赖注入特性来优雅实现。

5.1 原始反面案例(使用instanceof
// ❌ 反面教材:每当新增支付方式,都要修改此处代码,违反开闭原则 public String pay(Object channel) { if (channel instanceof Alipay) { // 支付宝逻辑 } else if (channel instanceof WechatPay) { // 微信逻辑 } throw new UnsupportedOperationException(); }
5.2 重构为策略模式(优雅实现)
第一步:定义策略接口
public interface PaymentStrategy { // 策略标识(用于前端传参,替代 instanceof 判断) String getType(); // 执行支付 void pay(Double amount); }
第二步:实现具体策略(交给 Spring 管理)
@Component public class AlipayStrategy implements PaymentStrategy { @Override public String getType() { return "ALIPAY"; } @Override public void pay(Double amount) { System.out.println("使用支付宝支付:" + amount); // 此处调用支付宝 SDK... } } @Component public class WechatStrategy implements PaymentStrategy { @Override public String getType() { return "WECHAT"; } @Override public void pay(Double amount) { System.out.println("使用微信支付:" + amount); // 此处调用微信 SDK... } }
第三步:创建策略上下文(核心工厂)

利用 Spring 的构造函数注入,将所有的策略List自动转换为Map<String, Strategy>此时已彻底消灭instanceof

@Component public class PaymentContext { private final Map<String, PaymentStrategy> strategyMap; @Autowired public PaymentContext(List<PaymentStrategy> strategies) { // 将 List 转为 Map,Key 为策略自定义的类型标识 this.strategyMap = strategies.stream() .collect(Collectors.toMap(PaymentStrategy::getType, Function.identity())); } // 对外统一调用接口 public void executePay(String type, Double amount) { PaymentStrategy strategy = strategyMap.get(type); if (strategy == null) { throw new IllegalArgumentException("不支持的支付方式: " + type); } strategy.pay(amount); } }
第四步:Controller 层调用
@RestController @RequestMapping("/api/pay") public class PayController { @Autowired private PaymentContext paymentContext; @PostMapping public String pay(@RequestParam String type, @RequestParam Double amount) { // 前端传入 "ALIPAY" 或 "WECHAT",无需任何类型判断 paymentContext.executePay(type, amount); return "支付成功"; } }
6. 策略模式 vsinstanceof的优势
对比维度if-else+instanceof策略模式(Map 路由)
开闭原则违反(新增渠道需修改原有类)符合(新增渠道只需新建类加@Component
代码可读性随着分支增多,逻辑臃肿难维护路由清晰,业务逻辑内聚在各自类中
测试难度需要 mock 大量无关分支可单独测试每个策略类
运行时性能链式判断,O(N) 复杂度Map 哈希查找,O(1) 复杂度
7. 联动避坑(Spring 代理问题)

如果具体策略类(如AlipayStrategy)本身被@Transactional@Cacheable注解,Spring 会为其生成代理对象。此时直接通过new创建实例会失去代理能力,必须通过 Spring 容器获取策略实例。这正是策略上下文(Context)通过依赖注入管理策略实例的重要原因。