【设计模式】策略模式:告别 if-else 地狱

当业务分支越来越多,if-else 会让代码难以维护。本文用前端真实场景讲清策略模式:抽象策略、上下文调用、动态切换与落地注意事项。

9 分钟阅读
小明

【设计模式】策略模式:告别 if-else 地狱

你一定见过这种代码:它最初只有 2 个分支,但半年后变成 12 个。谁改谁怕。

把“变化的算法”从主流程里拆出去,让主流程只负责调度。


1. 策略模式是什么

三个角色:

  1. Strategy(策略):同一接口,不同实现
  2. Concrete Strategy(具体策略):真正的算法/业务逻辑
  3. Context(上下文):根据条件选择并执行策略

它的价值是:

  • 开闭原则:新增策略,不动原有主流程
  • 单一职责:每个策略只做一件事

2. 一个支付场景的落地例子

type Order = { id: string; amount: number }
type PayResult = { success: boolean; message: string }

interface PayStrategy {
  pay(order: Order): Promise<PayResult>
}

class WechatPayStrategy implements PayStrategy {
  async pay(order: Order): Promise<PayResult> {
    return { success: true, message: `微信支付成功: ${order.id}` }
  }
}

class AlipayStrategy implements PayStrategy {
  async pay(order: Order): Promise<PayResult> {
    return { success: true, message: `支付宝支付成功: ${order.id}` }
  }
}

class BankPayStrategy implements PayStrategy {
  async pay(order: Order): Promise<PayResult> {
    return { success: true, message: `银行卡支付成功: ${order.id}` }
  }
}

class PayContext {
  constructor(private strategy: PayStrategy) {}

  setStrategy(strategy: PayStrategy) {
    this.strategy = strategy
  }

  async execute(order: Order) {
    return this.strategy.pay(order)
  }
}

调用时:

const strategyMap = {
  wechat: new WechatPayStrategy(),
  alipay: new AlipayStrategy(),
  bank: new BankPayStrategy()
}

const context = new PayContext(strategyMap.wechat)
await context.execute({ id: 'A1001', amount: 199 })

新增 applePay 时,只需要:

  • 新建一个 ApplePayStrategy
  • 在映射表注册

主流程不需要再加一层 else if


3. 什么时候该用策略模式

适合:

  • 同一个业务动作有多种实现
  • 分支会持续增加
  • 需要按配置或运行时动态切换

不适合:

  • 只有 2 个稳定分支,且未来基本不会变
  • 过度抽象导致代码跳转层级过深

4. 前端里常见的策略模式场景

  1. 表单校验规则(不同字段不同校验策略)
  2. 价格计算(会员价/活动价/券后价)
  3. 埋点上报(不同平台不同上报实现)
  4. 列表排序(按时间/热度/权重)

5. 两个实践建议

  1. 先接口再实现:先稳定策略接口,避免后续频繁改 Context。
  2. 策略注册集中管理:建议用 strategyMap 统一注册,避免分散在各处。

6. 策略模式的几个常见问题

问题 1:如果每个策略都需要初始化参数呢?

// 不推荐:每次都 new,容易浪费资源
const pay = () => {
  const strategy = new WechatPayStrategy(config.wechat)
  return context.execute(strategy, order)
}

// 推荐:工厂函数 + 单例缓存
const strategyFactory = new Map<string, PayStrategy>()

function getStrategy(type: string): PayStrategy {
  if (!strategyFactory.has(type)) {
    if (type === 'wechat') {
      strategyFactory.set(type, new WechatPayStrategy(config.wechat))
    } else if (type === 'alipay') {
      strategyFactory.set(type, new AlipayStrategy(config.alipay))
    }
  }
  return strategyFactory.get(type)!
}

问题 2:策略之间有通用逻辑怎么办?

使用继承或组合:

// 继承共同基类
abstract class BasePayStrategy implements PayStrategy {
  protected async log(msg: string) {
    console.log(`[Pay] ${msg}`)
  }
  
  abstract pay(order: Order): Promise<PayResult>
}

class WechatPayStrategy extends BasePayStrategy {
  async pay(order: Order): Promise<PayResult> {
    await this.log('开始微信支付')
    // 实现具体逻辑
    return { success: true, message: `微信支付成功` }
  }
}

问题 3:怎么监控/统计各策略的执行情况?

装饰器模式包装:

class MonitoredPayStrategy implements PayStrategy {
  constructor(private innerStrategy: PayStrategy) {}

  async pay(order: Order): Promise<PayResult> {
    const start = Date.now()
    try {
      const result = await this.innerStrategy.pay(order)
      console.log(`支付成功,耗时 ${Date.now() - start}ms`)
      return result
    } catch (error) {
      console.error(`支付失败,耗时 ${Date.now() - start}ms`, error)
      throw error
    }
  }
}

// 使用:
const monthtored = new MonitoredPayStrategy(new WechatPayStrategy())
await context.execute(monthtored, order)

策略模式不是为了“炫技”,而是为了把变化隔离。

当你发现 if-else 持续膨胀、每次加分支都要改主流程时,基本就是策略模式该上场的信号。