策略模式和 if-else 相比有什么好处?

#中间件#面试八股

策略模式和 if-else 相比有什么好处?

结论先行:策略模式的好处不在"代码更短",而在变化成本和可测试性:新增一种算法只加一个类(开闭原则),每个算法可独立单测,深层嵌套被摊平成注册表。代价是类变多、跳转变散。所以分支少且稳定就写 if-else,分支会持续增长才上策略。

1. if-else 的真实成本

症状表现后果
横向膨胀每加一个渠道/规则就多一个 else if方法动辄两百行,改一次要通读全文
纵向嵌套校验、权限、渠道交叉判断if 套四层,圈复杂度爆表
复用为零同一段逻辑复制到三个方法修一个 bug 要改三处
无法测试想测某个分支必须先构造整套前置条件单测覆盖率上不去
容易被改错缺 break、条件写反、优先级错线上事故高发区

2. 两种分发方式的本质差别

   if-else:顺序判定,命中即止(O(n) 比较)        策略 + 注册表:一次查表(O(1))

   amount ──► type == "A"? ──是──► 算法A            amount ──► REG.get(type) ──► 算法
                  │否                                        ▲
                  ▼                                          │
              type == "B"? ──是──► 算法B            REG: {"A":A, "B":B, "C":C}
                  │否                              新增算法= put 一条,不改判定逻辑
                  ▼
              type == "C"? ──是──► 算法C
                  │否
                  ▼
              抛异常 / 默认

左边是"逻辑里编码分支",右边是"数据里描述分支"。后者把变化从代码搬到了配置。

3. if-else 版本

public long discount(String type, long amount, User user) {
    if ("full_reduction".equals(type)) {
        return amount >= 100_00 ? amount - 10_00 : amount;
    } else if ("percent_off".equals(type)) {
        return amount * 85 / 100;
    } else if ("new_user".equals(type)) {
        return user.isNew() ? amount - 20_00 : amount;
    } else if ("vip".equals(type)) {
        return user.isVip() ? (long) (amount * 0.8) : amount;
    } else {
        throw new IllegalArgumentException("未知优惠类型: " + type);
    }
}

看着还行?再加上"叠加规则、互斥校验、按渠道限用、需要埋点统计每个策略的效果"之后,这个方法会变成三百行的怪物——每加一条规则都要动它,每次改动都可能影响其他分支。

4. 策略版本

interface Discount {
    long apply(long amount, User user);
    String type();                       // 自我声明类型,注册表按它建索引
}

class PercentOff implements Discount {
    public long apply(long amount, User user) { return amount * 85 / 100; }
    public String type() { return "percent_off"; }
}

class VipOff implements Discount {
    public long apply(long amount, User user) { return user.isVip() ? (long) (amount * 0.8) : amount; }
    public String type() { return "vip"; }
}

class DiscountRegistry {
    private static final Map<String, Discount> REG = new HashMap<>();

    static void register(Discount d) { REG.put(d.type(), d); }
    static Discount get(String type) {
        Discount d = REG.get(type);
        if (d == null) throw new IllegalArgumentException("未知优惠类型: " + type);   // 兜底必须显式
        return d;
    }
}

// 新增一种"直播专享":只加一个类 + 一行 register,其余代码零改动

5. 客观对比

维度if-else策略模式
新增一种算法改原方法(可能影响老分支)新增类 + 注册(不改老代码)
可读性(分支 ≤3)更好,一眼看完偏差,要跳文件
可读性(分支 ≥8)灾难更好,每个类只关心自己
单测难隔离,需造前置状态每个策略独立测试
复用靠复制粘贴直接注入复用
性能少量比较,可忽略一次哈希查找,可忽略
类数量0 新增N 个策略类 + 1 个注册表
调试断点在同一方法,直观需在多文件间跳转

6. 什么时候别改成策略

  • 分支数量少且稳定:三五个固定类型,switch 比五个类更好维护。
  • 分支逻辑只有一两行:抽成类后样板代码比逻辑还多。
  • 分支之间有强耦合顺序:此时可能更适合责任链而不是策略。
  • 一次性的脚本或管理端小功能:抽象收益抵不过理解成本。

判断标准:这个 if 在过去半年改过几次?未来半年还会不会改? 改过三次以上,就值得抽。

7. 常见追问

Q:策略模式和状态模式相比呢? 策略由外部选(调用方传 type),状态由内部迁(处理完自动转到下一状态)。订单流转用状态,计价规则用策略。

Q:换成枚举 + 函数式接口算不算策略? 算同一种思想。算法短、无外部依赖时,枚举常量持有 Function 更紧凑;算法需要注入 DAO、调用 RPC 时,还是独立类 + Spring 容器管理更合适。

Q:策略模式会不会只是把 if-else 搬到工厂里? 会——如果只写 Map 而不做"每个策略一个类",就是把分支换成了查表,可读性提升有限。真正的收益来自算法被封进独立的类:可单测、可复用、可单独发布。

Q:怎么避免策略类爆炸? 按业务域分组(计价、风控、路由各一套注册表),而不是一个大注册表塞三十个策略;注册表用 Map 而非 List 遍历匹配。

小结

  • 策略换掉的是"变化成本",不是代码长度;收益在开闭、隔离测试、消嵌套。
  • 用一张对比表说明代价:类数量、跳转、调试成本。
  • 分支少且稳定就别改;判断依据是"这段分支的修改频率"。
  • 与状态模式的分水岭:谁决定用哪个实现——外部选择 vs 内部迁移。
笔记加载中…