打不同的怪用不同的招:策略模式到底什么时候该用

2 阅读1分钟

打不同的怪用不同的招:策略模式到底什么时候该用

真实场景

我刚工作那会儿写过一个功能:根据用户的会员等级计算折扣。

普通用户打9折,VIP打8折,SVIP打7折。写出来的代码大概是这样:

public double getDiscount(String level) {
    if ("NORMAL".equals(level)) {
        return 0.9;
    } else if ("VIP".equals(level)) {
        return 0.8;
    } else if ("SVIP".equals(level)) {
        return 0.7;
    }
    return 1.0;
}

看着没什么问题。然后产品经理跑来说,我们加个"超级VIP"打6折。改。过两周又说要加个"学生用户"打85折。又改。再过一个月说要加个"企业用户"根据消费额度动态算折扣。

每加一种用户,这个if-else就长一截。更可怕的是,其他地方也有类似的判断逻辑——下单页面要判断、结算页面要判断、优惠券系统也要判断。改一个地方忘改另一个就是bug。

这就是策略模式要解决的问题。


策略模式长什么样

先定义一个策略接口:

public interface DiscountStrategy {
    double getDiscount();
}

然后每种用户类型实现一个策略:

public class NormalDiscount implements DiscountStrategy {
    public double getDiscount() { return 0.9; }
}

public class VipDiscount implements DiscountStrategy {
    public double getDiscount() { return 0.8; }
}

public class SvipDiscount implements DiscountStrategy {
    public double getDiscount() { return 0.7; }
}

最后用一个上下文类来管理:

public class DiscountContext {
    private DiscountStrategy strategy;

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

    public double calculatePrice(double price) {
        return price * strategy.getDiscount();
    }
}

用的时候:

DiscountContext ctx = new DiscountContext(new VipDiscount());
double finalPrice = ctx.calculatePrice(100); // 80

要加新的折扣类型?新增一个策略类就行,不需要改已有的任何代码。这就是开闭原则。


你真的看懂了吗

上面的例子教科书味道太重了,换一个更接地气的场景。

Java里的 Comparator 就是策略模式。你要给一个List排序,不同的排序方式就是不同的策略:

// 按年龄升序
list.sort(Comparator.comparingInt(Person::getAge));

// 按名字排序
list.sort(Comparator.comparing(Person::getName));

// 先按年龄再按名字
list.sort(Comparator.comparingInt(Person::getAge)
                      .thenComparing(Person::getName));

你没写任何if-else,排序方式随时换,而且随时可以定义新的排序规则。这就是策略模式在实际开发中最常见的用法。

Thread的Runnable也是。你传不同的Runnable实现,线程就执行不同的逻辑。


和if-else怎么选

不是所有if-else都要改成策略模式。滥用设计模式比不用更糟糕。

该用策略模式的信号

  • 同一个逻辑有多个变体,而且以后还可能继续加
  • 这些变体经常需要切换或者组合
  • 同样的判断逻辑在多个地方重复出现

不该用的场景

  • 只有二三种情况,而且不太会变了
  • 判断逻辑极其简单,不值得抽成单独的类
  • 团队里其他人都看不懂策略模式,你一个人炫技没意义

我见过有人把只有两个分支的if-else硬生生改成了策略模式,多了四五个类,代码从5行变成了100多行。完全没必要。


一个容易忽略的点

策略模式配合工厂模式或者Spring的依赖注入使用,效果最好。

比如用Spring的话:

@Service
public class NormalDiscount implements DiscountStrategy {
    public double getDiscount() { return 0.9; }
}

@Service
public class VipDiscount implements DiscountStrategy {
    public double getDiscount() { return 0.8; }
}

然后在业务代码里直接注入需要的策略就行,连new都不用。这也是Spring项目里策略模式最常见的用法。


回到最初的问题

如果让我重新写那个折扣计算的功能,我会这么做:定义DiscountStrategy接口,每种用户类型一个实现类,用Spring注入。如果折扣规则简单就用枚举或者Map来管理,不一定要上升到策略模式。

技术选型的核心是看场景,不是看设计模式书上怎么写的。

微信搜「爪爪代码冒险记」,我把23种设计模式都画成了漫画闯关游戏,每个模式一个故事,比背教科书有意思。