如何使用策略模式消除代码中复杂的判断?

wen java案例 62

让复杂判断逻辑“烟消云散”的实战指南

关键词:策略模式、消除if-else、代码重构、设计模式、可扩展性

如何使用策略模式消除代码中复杂的判断?


目录导读

  1. 困境重现:复杂判断为什么是代码的“坏味道”?
  2. 策略模式核心思想:从“指挥官”到“调度员”
  3. 实战拆解:一个支付场景的重构进化史
    • 1 原始代码:if-else的“叠罗汉”
    • 2 策略模式重构:抽象、实现与上下文
    • 3 为什么这样改更好?——三个维度分析
  4. 策略模式的“黄金使用场景”与反模式
  5. 问答环节:开发者最常问的6个策略模式问题
  6. 拓展进阶:策略模式 + 工厂模式 = 优雅双雄
  7. SEO友好总结:记住这四个字就够了

困境重现:复杂判断为什么是代码的“坏味道”?

你是否有过这样的经历:接手一个老项目,打开一个核心方法,发现里面是一串长达200行的if-else或者switch-case?看起来像这样:

public double calculate(String type, double amount) {
    if ("VIP".equals(type)) {
        return amount * 0.8;
    } else if ("GOLD".equals(type)) {
        return amount * 0.9;
    } else if ("SILVER".equals(type)) {
        return amount * 0.95;
    } else if ("NEW_USER".equals(type)) {
        return amount * 0.85;
    } else {
        return amount;
    }
}

这种代码在初期看似简单直接,但随着业务迭代,它会变成团队的噩梦

  • 每个新类型都要加一个else if,方法越变越长
  • 修改一处逻辑可能影响其他分支(违反开闭原则)
  • 单元测试需要覆盖所有分支,维护成本指数级上升
  • 代码可读性极差,新人接手需要从头到尾读完所有条件

核心痛点:我们把“业务类型”和“应用逻辑”强行耦合在一起,导致一旦业务变化,就必须修改已有代码。


策略模式核心思想:从“指挥官”到“调度员”

策略模式(Strategy Pattern)的定义非常简洁:定义一组算法,将每个算法封装起来,并使它们可以互相替换

通俗理解:我们不再让一个“指挥官”(大方法)亲自处理所有类型的逻辑,而是创建一个“调度员”(上下文类),根据传入的类型,自动选择对应的“士兵”(策略类)去执行任务。

三个关键角色

  1. 策略接口(Strategy):定义所有策略必须遵守的“行动契约”
  2. 具体策略(ConcreteStrategy):实现接口的具体逻辑
  3. 上下文(Context):持有策略引用,负责调用策略执行

实战拆解:一个支付场景的重构进化史

1 原始代码:if-else的“叠罗汉”

假设我们要实现一个支付系统,支持支付宝、微信、银联三种支付方式:

public class PaymentService {
    public void pay(String channel, double amount) {
        if ("alipay".equals(channel)) {
            System.out.println("调用支付宝API,金额:" + amount);
            // 50行具体逻辑
        } else if ("wechat".equals(channel)) {
            System.out.println("调用微信支付API,金额:" + amount);
            // 50行具体逻辑
        } else if ("unionpay".equals(channel)) {
            System.out.println("调用银联API,金额:" + amount);
            // 50行具体逻辑
        } else {
            throw new RuntimeException("不支持的支付方式");
        }
    }
}

这种代码:

  • 每增加一种支付方式,都要修改pay方法
  • 不同支付方式的逻辑无法复用
  • 单元测试需要模拟三种支付API,测试复杂

2 策略模式重构:抽象、实现与上下文

第一步:定义策略接口

public interface PaymentStrategy {
    void pay(double amount);
}

第二步:实现具体策略

public class AlipayStrategy implements PaymentStrategy {
    @Override
    public void pay(double amount) {
        System.out.println("调用支付宝API,金额:" + amount);
        // 支付宝特有逻辑
    }
}
public class WechatPayStrategy implements PaymentStrategy {
    @Override
    public void pay(double amount) {
        System.out.println("调用微信支付API,金额:" + amount);
        // 微信特有逻辑
    }
}
public class UnionPayStrategy implements PaymentStrategy {
    @Override
    public void pay(double amount) {
        System.out.println("调用银联API,金额:" + amount);
        // 银联特有逻辑
    }
}

第三步:创建上下文

public class PaymentContext {
    private PaymentStrategy strategy;
    public PaymentContext(PaymentStrategy strategy) {
        this.strategy = strategy;
    }
    public void executePay(double amount) {
        strategy.pay(amount);
    }
}

第四步:客户端调用

public class Client {
    public static void main(String[] args) {
        // 原来的if-else完全消失了!
        String channel = "alipay";
        PaymentStrategy strategy = PaymentStrategyFactory.getStrategy(channel);
        PaymentContext context = new PaymentContext(strategy);
        context.executePay(100.0);
    }
}

3 为什么这样改更好?——三个维度分析

维度 重构前(if-else) 重构后(策略模式)
开闭原则 新增支付方式必须修改pay方法 新增类实现接口即可,无需修改已有代码
可测试性 需要模拟整个pay方法 每个策略可单独测试,mock简单
可读性 200行的大方法难以理解 每个策略类只关注自己的逻辑

关键变化:业务类型(channel字符串)与业务逻辑(支付步骤)被解耦,新增支付方式时,只需新建一个策略类,并在工厂中注册即可。


策略模式的“黄金使用场景”与反模式

✅ 适合使用策略模式的场景

  • 一个系统中存在大量相互替代的算法或行为
  • 算法之间的区别仅在于内部实现,但对外接口一致
  • 需要避免使用大量条件分支(if-else / switch-case)
  • 算法需要动态切换(根据用户等级选择不同折扣)

❌ 不适合使用策略模式的场景

  • 算法数量极少(比如就2个),且永远不会增加
  • 算法逻辑极其简单,使用策略模式反而增加代码复杂度
  • 算法之间有大量共享状态(此时考虑模板方法模式更合适)

核心判断标准如果你不确定未来会不会增加新的分支,那就用策略模式,未来增加分支的概率越高,策略模式的回报越大。


问答环节:开发者最常问的6个策略模式问题

Q1:策略模式会创建大量类,会不会导致类爆炸?

A:这是一个合理的担忧,但实践中,每个策略类的代码通常很少(几十行),类数量的增加是可控的,而且可以通过策略模式 + 枚举策略模式 + 注册表来减少类数量,如果你有100种折扣策略,但其中90%的逻辑是相同的,可以用参数化策略:

public class DiscountStrategy implements Strategy {
    private double discountRate;
    public DiscountStrategy(double rate) { this.discountRate = rate; }
    // 只需一个策略类,但通过构造参数区分
}

Q2:客户端怎么知道该用哪个策略?是不是还得写if-else?

A:策略的获取逻辑确实需要一个地方来做选择,但这个选择通常被封装进工厂模式注册表,客户端只需要传入一个类型标识(如枚举或字符串),工厂帮你找到正确的策略,这样,选择逻辑被集中在工厂中,主业务逻辑不受影响。

Q3:策略模式能不能消除所有if-else?

A:不能,策略模式消除的是业务逻辑分支,但路由逻辑(选择哪个策略)仍然需要条件判断,不过路由逻辑通常很薄(一个map的get操作),维护成本远低于业务分支,目标不是消除所有判断,而是把判断从业务逻辑中隔离出去

Q4:策略模式比if-else性能差吗?

A:通常不会,策略模式只是多了一次接口调用和一次map查询(O(1)),相对于数据库查询或网络IO,这些开销可以忽略不计,如果你有性能洁癖,可以用枚举+函数式接口来替代继承。

Q5:在JavaScript中如何实现策略模式?

A:JavaScript可以利用函数是一等公民的特性,用更简洁的方式实现:

const strategies = {
    'alipay': (amount) => console.log(`支付宝支付${amount}`),
    'wechat': (amount) => console.log(`微信支付${amount}`),
};
function pay(channel, amount) {
    const strategy = strategies[channel];
    if (!strategy) throw new Error('不支持的支付方式');
    strategy(amount);
}

Q6:策略模式与工厂模式的区别是什么?

A:这是初学者最易混淆的两个模式,简单说:

  • 工厂模式:负责创建对象(生产策略)
  • 策略模式:负责执行算法(使用策略)

通常两者结合使用:工厂根据输入创建策略对象策略对象执行具体逻辑


拓展进阶:策略模式 + 工厂模式 = 优雅双雄

上面的支付示例中,我们提到了PaymentStrategyFactory,这其实就是工厂模式与策略模式的经典组合,它的实现可以是:

public class PaymentStrategyFactory {
    private static final Map<String, PaymentStrategy> strategies = new HashMap<>();
    static {
        strategies.put("alipay", new AlipayStrategy());
        strategies.put("wechat", new WechatPayStrategy());
        strategies.put("unionpay", new UnionPayStrategy());
    }
    public static PaymentStrategy getStrategy(String channel) {
        PaymentStrategy strategy = strategies.get(channel);
        if (strategy == null) {
            throw new IllegalArgumentException("不支持的支付方式: " + channel);
        }
        return strategy;
    }
}

效果:客户端调用变成一行代码:

paymentService.executePay(PaymentStrategyFactory.getStrategy(channel), amount);

这种组合的好处:

  • 策略的选择逻辑完全从业务代码中剥离
  • 新增支付方式:只需在工厂的put中添加一行,在策略目录下新增一个类
  • 可以实现策略的懒加载缓存,提升性能

SEO友好总结:记住这四个字就够了

如果你只从这篇文章带走一句话,那就是:“把变的部分封装起来,把不变的稳定下来”

策略模式的本质是为“变化点”建立抽象层,让新业务以“插件”的形式接入系统,而不用动已有逻辑,这种思想不仅适用于支付、折扣、排序等经典场景,也适用于消息推送、数据导出、文件解析、校验规则等几乎所有存在分支判断的场景。

行动清单

  1. 下次遇到超过3个if-else,思考是否可以提取成策略
  2. 先定义策略接口,再实现具体策略,最后用工厂组装
  3. 单元测试时,每个策略独立测试,覆盖率轻松达到100%

推荐阅读

  • 想深入了解设计模式,可以搜索“GoF 23种设计模式 精讲”
  • 想学实战技巧,可以看“重构:改善既有代码的设计”中关于条件分支的部分

*本文由编程架构师团队原创,遵循知识共享-署名-非商业性使用-相同方式共享协议,转载请注明出处:码农架构宝典(请勿添加实际域名,此处为示例)

抱歉,评论功能暂时关闭!