策略模式

一段代码引发的架构灾难

我们团队曾经有一段"经典"代码,在三年里被改了 47 次,最终成了一个 2000 行的怪物:

public class DiscountCalculator {
    public double calculate(String userType, String promotionType, double amount) {
        if ("VIP".equals(userType)) {
            if ("FULLCUT".equals(promotionType)) {
                return amount >= 200 ? amount - 50 : amount;
            } else if ("DISCOUNT".equals(promotionType)) {
                return amount * 0.8;
            } else if ("COUPON".equals(promotionType)) {
                return amount - 30;
            }
        } else if ("SVIP".equals(userType)) {
            if ("FULLCUT".equals(promotionType)) {
                return amount >= 100 ? amount - 80 : amount;
            } else if ("DISCOUNT".equals(promotionType)) {
                return amount * 0.7;
            } else if ("COUPON".equals(promotionType)) {
                return amount - 50;
            }
        } else if ("NORMAL".equals(userType)) {
            if ("FULLCUT".equals(promotionType)) {
                return amount >= 300 ? amount - 30 : amount;
            } else if ("DISCOUNT".equals(promotionType)) {
                return amount * 0.95;
            } else if ("COUPON".equals(promotionType)) {
                return amount - 10;
            }
        }
        return amount;
    }
}

每次加一个用户类型或促销类型,都要在这堆 if-else 里再加分支。测试?每次改完都怕影响其他分支。

策略模式解决的就是这个问题:把每种算法/策略封装成独立类,让它们可以相互替换。


二、策略模式核心结构

2.1 标准写法

// 策略接口
interface DiscountStrategy {
    double calculate(double amount);
}

// 具体策略:满减
class FullCutStrategy implements DiscountStrategy {
    private final double threshold;
    private final double cut;

    public FullCutStrategy(double threshold, double cut) {
        this.threshold = threshold;
        this.cut = cut;
    }

    @Override
    public double calculate(double amount) {
        return amount >= threshold ? amount - cut : amount;
    }
}

// 具体策略:打折
class DiscountStrategyImpl implements DiscountStrategy {
    private final double rate;

    public DiscountStrategyImpl(double rate) {
        this.rate = rate;
    }

    @Override
    public double calculate(double amount) {
        return amount * rate;
    }
}

// 具体策略:立减
class DirectCutStrategy implements DiscountStrategy {
    private final double cut;

    public DirectCutStrategy(double cut) {
        this.cut = cut;
    }

    @Override
    public double calculate(double amount) {
        return amount - cut;
    }
}

2.2 上下文类

class DiscountContext {
    private DiscountStrategy strategy;

    // 运行时注入策略
    public void setStrategy(DiscountStrategy strategy) {
        this.strategy = strategy;
    }

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

2.3 使用方式

DiscountContext context = new DiscountContext();

// VIP 用户用满减
context.setStrategy(new FullCutStrategy(200, 50));
double vipPrice = context.calculate(300); // 250

// 普通用户用打折
context.setStrategy(new DiscountStrategyImpl(0.95));
double normalPrice = context.calculate(300); // 285

现在,新增促销方式不需要改任何现有代码,只需要加一个新类

// 新增策略:买二送一(折算折扣)
class BuyTwoGetOneStrategy implements DiscountStrategy {
    private final double perUnitPrice;
    private final int totalQuantity;

    public BuyTwoGetOneStrategy(double perUnitPrice, int totalQuantity) {
        this.perUnitPrice = perUnitPrice;
        this.totalQuantity = totalQuantity;
    }

    @Override
    public double calculate(double amount) {
        int freeItems = totalQuantity / 3;
        double effectiveQuantity = totalQuantity - freeItems;
        return effectiveQuantity * perUnitPrice;
    }
}

三、策略模式 + 工厂模式🔴

3.1 消除 if-else 的工厂

class DiscountStrategyFactory {
    private static final Map<String, DiscountStrategy> STRATEGIES = new HashMap<>();

    static {
        STRATEGIES.put("FULLCUT_VIP", new FullCutStrategy(200, 50));
        STRATEGIES.put("DISCOUNT_SVIP", new DiscountStrategyImpl(0.7));
        // 可以从配置文件/数据库加载
    }

    public static DiscountStrategy getStrategy(String type) {
        DiscountStrategy strategy = STRATEGIES.get(type);
        if (strategy == null) {
            throw new IllegalArgumentException("Unknown strategy: " + type);
        }
        return strategy;
    }
}

配合 Spring,使用 @Component 自动注册:

@Configuration
public class StrategyConfig {
    @Bean
    public Map<String, DiscountStrategy> discountStrategies(
            List<DiscountStrategy> strategyList) {
        Map<String, DiscountStrategy> map = new HashMap<>();
        for (DiscountStrategy s : strategyList) {
            // 用策略类的简单类名作为 key
            String name = s.getClass().getSimpleName()
                .replace("Strategy", "").toLowerCase();
            map.put(name, s);
        }
        return map;
    }
}

@Service
class DiscountService {
    private final Map<String, DiscountStrategy> strategies;

    public DiscountService(Map<String, DiscountStrategy> strategies) {
        this.strategies = strategies;
    }

    public double calculate(String type, double amount) {
        return strategies.get(type).calculate(amount);
    }
}

3.2 策略模式的 OCP 实现

新增促销类型时:
- if-else 方案:修改 DiscountCalculator 类(违反 OCP)
- 策略模式方案:新增 Strategy 类,修改工厂(遵守 OCP)

【架构权衡】 策略模式的代价是类数量增加。每个策略一个类,10 个策略就是 10 个类。相比于 if-else 集中在一个文件里,策略模式让代码更分散。这是可扩展性和代码集中性的权衡。


四、生产实战场景🟡

4.1 支付渠道选择

interface PaymentStrategy {
    PaymentResult pay(PaymentRequest request);
}

class AlipayStrategy implements PaymentStrategy { ... }
class WechatPayStrategy implements PaymentStrategy { ... }
class UnionPayStrategy implements PaymentStrategy { ... }
class CreditCardStrategy implements PaymentStrategy { ... }

@Service
class PaymentService {
    private final Map<String, PaymentStrategy> strategies;

    public PaymentService(Map<String, PaymentStrategy> strategies) {
        this.strategies = strategies;
    }

    public PaymentResult pay(String channel, PaymentRequest request) {
        PaymentStrategy strategy = strategies.get(channel);
        if (strategy == null) {
            throw new IllegalArgumentException("Unsupported channel: " + channel);
        }
        return strategy.pay(request);
    }
}

4.2 排序策略

interface SortStrategy<T> {
    List<T> sort(List<T> data);
}

class AscendingSortStrategy<T extends Comparable<T>> implements SortStrategy<T> {
    @Override
    public List<T> sort(List<T> data) {
        return data.stream().sorted().collect(Collectors.toList());
    }
}

class DescendingSortStrategy<T extends Comparable<T>> implements SortStrategy<T> {
    @Override
    public List<T> sort(List<T> data) {
        return data.stream()
            .sorted(Comparator.reverseOrder())
            .collect(Collectors.toList());
    }
}

class PriceSortStrategy implements SortStrategy<Product> {
    @Override
    public List<Product> sort(List<Product> data) {
        return data.stream()
            .sorted(Comparator.comparing(Product::getPrice))
            .collect(Collectors.toList());
    }
}

class SalesVolumeSortStrategy implements SortStrategy<Product> {
    @Override
    public List<Product> sort(List<Product> data) {
        return data.stream()
            .sorted(Comparator.comparing(Product::getSalesVolume).reversed())
            .collect(Collectors.toList());
    }
}

4.3 验证策略

interface ValidationStrategy<T> {
    boolean validate(T target);
    String getErrorMessage();
}

class EmailValidationStrategy implements ValidationStrategy<String> {
    private static final Pattern EMAIL_PATTERN =
        Pattern.compile("^[A-Za-z0-9+_.-]+@(.+)$");

    @Override
    public boolean validate(String email) {
        return email != null && EMAIL_PATTERN.matcher(email).matches();
    }

    @Override
    public String getErrorMessage() {
        return "Invalid email format";
    }
}

class PasswordValidationStrategy implements ValidationStrategy<String> {
    @Override
    public boolean validate(String password) {
        return password != null
            && password.length() >= 8
            && password.matches(".*[A-Z].*")
            && password.matches(".*[0-9].*");
    }

    @Override
    public String getErrorMessage() {
        return "Password must be at least 8 characters with uppercase and digit";
    }
}

五、策略模式 vs 状态模式

很多人分不清策略模式和状态模式。

维度策略模式状态模式
使用者意图选择不同的算法/策略对象状态变化时行为变化
切换方式外部指定(客户端决定)内部驱动(状态自动切换)
状态间关系策略之间相互独立状态之间有转换关系
上下文依赖上下文不需要知道策略的切换上下文需要管理状态转换
典型场景支付方式、排序算法订单状态、流程审批
// 策略模式:客户端决定用哪个策略
DiscountStrategy s = new VipDiscountStrategy();
context.setStrategy(s);
context.calculate(100);

// 状态模式:内部自动切换状态
class Order {
    private OrderState state;

    public void pay() {
        state = state.pay(); // 状态自动切换到下一个
    }
}

六、生产避坑清单

6.1 策略选择逻辑的 if-else

// ❌ 错误:策略工厂里还有 if-else,等于没改
class DiscountStrategyFactory {
    public static DiscountStrategy get(String type) {
        if ("VIP".equals(type)) {
            return new VipStrategy();
        } else if ("SVIP".equals(type)) {
            return new SvipStrategy();
        }
        // 还是 if-else
    }
}

// ✅ 正确:用 Map 替代 if-else
class DiscountStrategyFactory {
    private static final Map<String, DiscountStrategy> MAP = new HashMap<>();
    static {
        MAP.put("VIP", new VipStrategy());
        MAP.put("SVIP", new SvipStrategy());
    }

    public static DiscountStrategy get(String type) {
        return MAP.get(type);
    }
}

6.2 策略类的数量爆炸

如果策略有 50 种,每种策略一个类会导致类爆炸。解决方案:

// 策略模板 + 参数化
class ParametricDiscountStrategy implements DiscountStrategy {
    private final DiscountType type;
    private final Map<String, Object> params;

    public ParametricDiscountStrategy(DiscountType type, Map<String, Object> params) {
        this.type = type;
        this.params = params;
    }

    @Override
    public double calculate(double amount) {
        switch (type) {
            case FULLCUT:
                double threshold = (double) params.get("threshold");
                double cut = (double) params.get("cut");
                return amount >= threshold ? amount - cut : amount;
            case DISCOUNT:
                double rate = (double) params.get("rate");
                return amount * rate;
            default:
                return amount;
        }
    }
}
💡

策略模式和模板方法模式可以结合使用。当策略之间有共同逻辑时,用模板方法提取骨架,策略只实现差异部分。


七、面试总结

7.1 核心追问

  1. "策略模式和 if-else 相比有什么优势?" —— OCP、开闭原则
  2. "策略模式如何消除分支判断?" —— 工厂 + Map
  3. "策略模式和状态模式的区别是什么?" —— 外部指定 vs 内部转换
  4. "策略模式有什么缺点?" —— 类数量增加、策略选择本身可能有 if-else

7.2 级别差异

级别期望回答
P5能写出策略模式的基本结构
P6能说出和工厂模式的结合,知道如何消除策略选择的 if-else
P7能对比策略模式和状态模式,能分析类数量爆炸的解决方案