策略模式:把算法装进“可插拔”的盒子里

策略模式,说白了就是——定义一系列算法,把它们一个个封装好,然后让它们可以互相替换。你想想看,我们写代码时经常遇到这种情况:同一个功能,有好几种实现方式。比如排序,你可以用冒泡、快排、归并;比如支付,你可以用微信、支付宝、银行卡。如果把这些逻辑全塞在一个大if-else里,那代码迟早变成一锅粥。

我个人习惯,遇到这种“多选一”的场景,第一反应就是策略模式。它不是什么高深的东西,但用好了,代码会变得特别清爽。

为什么需要策略模式?

先看一个反面教材。假设你写了一个订单系统,要根据用户等级计算折扣:

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

这段代码有什么问题?

  • 每次新增一个等级,都要改这个函数
  • 多个地方用到折扣计算时,逻辑会重复
  • 测试起来很麻烦,得把所有分支跑一遍

我在项目中遇到过类似的情况。当时是一个促销活动模块,运营同学每周都要调整折扣策略。每次改代码、发版,折腾得够呛。后来我重构成了策略模式,运营直接在后台配置策略类,再也不用等开发排期了。

策略模式的核心结构

策略模式其实就三个角色:

  • 策略接口:定义所有算法共有的方法
  • 具体策略:实现接口,封装各自的算法
  • 上下文:持有策略接口的引用,负责调用

嗯,这里要注意:上下文不负责实现算法,它只负责“用”算法。就像你点外卖,你只关心吃什么(调用策略),不关心厨师怎么做(算法实现)。

核心思想:把算法的定义和使用分离。算法可以独立变化,客户端不用关心具体实现。

用代码说话

还是刚才的折扣计算,我们用策略模式重写一遍。

先定义策略接口:

public interface DiscountStrategy {
    double calculate(double amount);
}

然后实现几个具体策略:

public class VIPDiscount implements DiscountStrategy {
    @Override
    public double calculate(double amount) {
        return amount * 0.8;
    }
}

public class GoldDiscount implements DiscountStrategy {
    @Override
    public double calculate(double amount) {
        return amount * 0.85;
    }
}

public class SilverDiscount implements DiscountStrategy {
    @Override
    public double calculate(double amount) {
        return amount * 0.9;
    }
}

最后是上下文:

public class OrderContext {
    private DiscountStrategy strategy;

    public OrderContext(DiscountStrategy strategy) {
        this.strategy = strategy;
    }

    public void setStrategy(DiscountStrategy strategy) {
        this.strategy = strategy;
    }

    public double getFinalAmount(double amount) {
        return strategy.calculate(amount);
    }
}

客户端调用:

OrderContext context = new OrderContext(new VIPDiscount());
double finalAmount = context.getFinalAmount(100.0);
// 输出 80.0

// 想换策略?直接换
context.setStrategy(new GoldDiscount());
finalAmount = context.getFinalAmount(100.0);
// 输出 85.0

你看,现在新增一个等级,只需要新增一个策略类,完全不用改现有代码。这就是开闭原则的体现——对扩展开放,对修改关闭。

策略模式的应用场景

我个人总结,以下情况特别适合用策略模式:

场景 说明
支付方式 微信、支付宝、银行卡、余额……每种支付逻辑不同
排序算法 根据数据量大小选择冒泡、快排、归并
数据导出 导出为Excel、PDF、CSV,格式不同但流程相似
促销活动 满减、打折、赠品、积分……策略经常变化
用户权限 不同角色看到不同菜单、执行不同操作

小提示:如果策略只有两三个,而且几乎不会变化,用简单的if-else反而更直观。别为了用模式而用模式。

避坑指南

我曾经在一个项目中踩过坑。当时把策略类全部用静态工厂方法创建,结果策略多了以后,工厂类变得巨大无比,每次新增策略都要改工厂。后来我改用注册表模式,把策略名称和类映射关系放在配置文件里,用反射动态加载。这样新增策略只需要加配置,完全不用改代码。

另外,策略模式有一个潜在问题:客户端必须知道所有策略的区别,才能选择合适的策略。如果策略太多,客户端会变得很重。这时候可以考虑结合简单工厂模式,把策略的选择逻辑封装起来。

注意:策略模式会增加类的数量。如果每个策略只有几行代码,却要新建一个类,那就过度设计了。可以用匿名内部类或Lambda表达式简化。

策略模式的核心逻辑图

下面这张图展示了策略模式的整体结构。你可以看到,上下文只依赖策略接口,具体策略之间互不干扰,可以独立替换。

策略模式结构图 OrderContext - strategy: DiscountStrategy <<接口>> DiscountStrategy 持有 VIPDiscount amount * 0.8 GoldDiscount amount * 0.85 SilverDiscount amount * 0.9 上下文持有策略接口,具体策略实现算法,运行时可以自由切换

策略模式的优缺点

任何模式都有两面性,策略模式也不例外。

优点:

  • 符合开闭原则,新增策略不用改现有代码
  • 避免大量的if-else,代码更清晰
  • 策略可以独立测试,提高可测性
  • 运行时可以动态切换策略,灵活性高

缺点:

  • 类数量增加,每个策略一个类
  • 客户端必须了解所有策略的区别
  • 策略之间无法共享状态(但可以通过上下文传递)

我的建议:策略模式最适合那些“算法经常变化”或“算法数量较多”的场景。如果算法稳定且数量少,别硬套模式。代码是给人读的,不是给模式看的。

总结

策略模式的核心就一句话:把算法封装成对象,让它们可以互相替换。它让我们的代码从“硬编码”走向“可配置”,从“修改”走向“扩展”。

我记得有一次重构一个支付模块,原来有十几个if-else,每次新增支付方式都要改核心代码。改成策略模式后,新增支付方式只需要写一个新类,然后在配置中心注册一下。运营同学自己就能上线新支付方式,再也不用半夜找我发版了。

嗯,这就是策略模式的价值——让变化变得简单,让代码变得干净


公众号:蓝海资料掘金营,微信 deep3321