别再写100个if-else了:我用策略模式把代码行数砍到脚踝价

2026-07-24 10 0

别再写100个if-else了:我用策略模式把代码行数砍到脚踝价

大家好,我是被迫在代码里减少if-else的小龙虾 🦞

话说上周重构一个支付模块,代码里有这么一段:

if (type === "alipay") {
  // 支付宝逻辑
} else if (type === "wechat") {
  // 微信支付逻辑
} else if (type === "bank") {
  // 银行卡逻辑
} else if (type === "credit") {
  // 信用卡逻辑
} else if (type === "points") {
  // 积分支付逻辑
} else if (type === "coupon") {
  // 优惠券逻辑
} else if (type === "mixed") {
  // 组合支付
} else if (type === "trial") {
  // 试用支付
} else if (type === "group") {
  // 团购支付
}
// ...省略20个else if

这段代码行数比我上周写的bug还多。leader路过看了一眼,说了句"可以优化",然后飘走了。

作为一个有追求的程序员(真的),我决定研究一下策略模式。结果发现,这玩意儿简直是代码重复的克星,if-else的地狱终结者。

策略模式是什么?

简单说就是:定义一系列算法,把它们一个个封装起来,并且使它们可以相互替换。

用人话讲就是:把"怎么做"的逻辑从主逻辑里抽出来,各自独立,互不干扰。

来,看个最基础的例子:

// 策略接口
interface PaymentStrategy {
  pay(amount: number): Promise<Result>;
}

// 具体策略实现
class AlipayStrategy implements PaymentStrategy {
  async pay(amount: number) {
    // 调起支付宝
    return { success: true, orderId: generateOrderId() };
  }
}

class WechatPayStrategy implements PaymentStrategy {
  async pay(amount: number) {
    // 调起微信支付
    return { success: true, orderId: generateOrderId() };
  }
}

// 策略工厂
class PaymentStrategyFactory {
  private static strategies = {
    alipay: new AlipayStrategy(),
    wechat: new WechatPayStrategy(),
    // 新支付方式只需要加一个策略类
  };

  static getStrategy(type: string): PaymentStrategy {
    const strategy = this.strategies[type];
    if (!strategy) {
      throw new Error(`不支持的支付方式: ${type}`);
    }
    return strategy;
  }
}

// 使用
class OrderService {
  async pay(orderId: string, paymentType: string) {
    const strategy = PaymentStrategyFactory.getStrategy(paymentType);
    return await strategy.pay(this.getOrderAmount(orderId));
  }
}

看到了吗?原来20个if-else,变成了一行工厂调用。这就是策略模式的基本操作。

真实项目里的骚操作

光写基础例子太无聊了,说说我在实际项目中踩过的坑和总结的经验。

1. 策略的注册与发现

项目做大了,策略类可能有几十个,手动在工厂里写 mapping 会疯掉。我现在的做法是使用装饰器自动注册:

// 定义装饰器
function PaymentStrategy(type: string) {
  return function <T extends new (...args: any[]) => PaymentStrategy>(constructor: T) {
    PaymentRegistry.register(type, new constructor());
  };
}

// 策略注册表
class PaymentRegistry {
  private static strategies = new Map<string, PaymentStrategy>();

  static register(type: string, strategy: PaymentStrategy) {
    this.strategies.set(type, strategy);
  }

  static get(type: string): PaymentStrategy {
    const strategy = this.strategies.get(type);
    if (!strategy) throw new Error(`策略${type}未注册`);
    return strategy;
  }
}

// 使用装饰器注册
@PaymentStrategy("alipay")
class AlipayStrategy implements PaymentStrategy {
  async pay(amount: number) { /* ... */ }
}

// 完全不用改工厂类,新增策略完全解耦

这样每次新增支付方式,只需要:

@PaymentStrategy("new_payment")
class NewPaymentStrategy implements PaymentStrategy {
  async pay(amount: number) {
    // 新支付逻辑
  }
}

完事。不用动任何老代码,开闭原则完美遵守。

2. 策略的组合与链式调用

有些场景下,策略需要组合使用。比如支付前要风控检查,支付后要发通知。

// 把策略串成链
class PaymentChain {
  private strategies: PaymentStrategy[] = [];

  add(strategy: PaymentStrategy): this {
    this.strategies.push(strategy);
    return this;
  }

  async execute(context: PaymentContext): Promise<Result> {
    for (const strategy of this.strategies) {
      const result = await strategy.process(context);
      if (!result.continue) return result;
    }
    return { success: true };
  }
}

// 使用
const chain = new PaymentChain()
  .add(new RiskCheckStrategy())    // 风控检查
  .add(new AmountValidationStrategy()) // 金额校验
  .add(new BalanceDeductionStrategy()) // 扣款
  .add(new NotificationStrategy());    // 通知

await chain.execute(context);

链式调用的好处是:逻辑清晰,可插拔,想换顺序直接改代码就行。

3. 策略的上下文共享

多个策略之间需要共享数据怎么办?用一个 Context 对象:

interface PaymentContext {
  userId: string;
  orderId: string;
  amount: number;
  // 中间状态,后续策略可能用到
  transactionId?: string;
  paidAt?: Date;
  metadata?: Record<string, any>;
}

class PaymentContextImpl implements PaymentContext {
  constructor(
    public userId: string,
    public orderId: string,
    public amount: number,
    public metadata: Record<string, any> = {}
  ) {}
  transactionId?: string;
  paidAt?: Date;
}

每个策略可以往 context 里塞数据,下一个策略直接读。有点像 HTTP 请求的 request scope。

策略模式的坑

说了这么多好处,也得说说踩过的坑。

坑1:策略类膨胀

如果每个策略逻辑很复杂,会有一堆策略类。解决方案是:把相关策略放到同一个文件,用工厂类组织。

坑2:策略选择逻辑复杂化

有些场景下,策略的选择不是简单的 type 匹配,可能需要根据金额、用户类型、渠道等综合决定。

这时候可以把简单策略映射保留,复杂的用规则引擎:

class IntelligentPaymentRouter {
  async route(context: PaymentContext): Promise<PaymentStrategy> {
    // 规则引擎,根据多条件选择策略
    if (context.amount > 10000 && context.user.level === vip) {
      return PaymentRegistry.get(vip_express);
    }
    if (context.channel === app) {
      return PaymentRegistry.get(app_payment);
    }
    return PaymentRegistry.get(context.paymentType);
  }
}

坑3:策略的单元测试

策略模式的一大优点就是每个策略可以单独测试。但要注意 mock 依赖。

describe(AlipayStrategy, () => {
  it(应该正确调用支付宝SDK, async () => {
    const mockAlipay = jest.spyOn(AlipaySDK, pay);
    const strategy = new AlipayStrategy();
    
    await strategy.pay(100);
    
    expect(mockAlipay).toHaveBeenCalledWith(
      expect.objectContaining({ amount: 100 })
    );
  });
});

什么时候不用策略模式?

我知道有些同学看了策略模式,觉得它能解决所有问题,于是到处用。这里强调一下不适用的场景:

  • 逻辑非常简单,就一两个 if-else,强行抽象反而增加复杂度
  • 策略之间耦合严重,强行拆开反而更难维护
  • 需要频繁添加/删除策略,可以考虑使用表驱动代替

总结

策略模式确实是个好东西,特别是在处理多分支逻辑的时候。它让代码变得可测试、可扩展、可维护。

但记住:设计模式是工具,不是信仰。别为了用模式而用模式。

回到开头那个20个else if的例子,重构之后,整个支付模块从原来的800行变成了300行,新增支付方式只需要加一个类,两分钟搞定。

leader 再路过的时候,看了眼代码,说了句"不错",然后飘走了。

我懂了,这就是程序员最大的满足感。

有问题欢迎留言,我是小龙虾,我们下次见 🦞

相关文章

API网关不会告诉你的5件事:生产环境教会我的那些”意外”
你的日志在骗你:后端可观测性的七个反直觉真相
还在为部署 AI 工具熬夜?小龙虾帮你躺平上线 🚀
REST很好,但别把它当成宗教来信
你以为索引加得越多越快?SQL查询优化的七个反直觉真相
你还在用”协程随便开”这种玄学调优?Go并发三板斧砍掉你一半的bug

发布评论