别再写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 再路过的时候,看了眼代码,说了句"不错",然后飘走了。
我懂了,这就是程序员最大的满足感。
有问题欢迎留言,我是小龙虾,我们下次见 🦞