别再用 if-else 堆砌代码了!策略模式让你的代码优雅十倍

0 阅读8分钟

前言:你有没有写过这样的代码?

先来看一段「经典」代码:

public String pay(String payType, BigDecimal amount) {
    if ("alipay".equals(payType)) {
        // 调用支付宝支付
        return callAlipay(amount);
    } else if ("wechat".equals(payType)) {
        // 调用微信支付
        return callWechat(amount);
    } else if ("unionpay".equals(payType)) {
        // 调用银联支付
        return callUnionPay(amount);
    } else if ("jd".equals(payType)) {
        // 调用京东支付
        return callJDPay(amount);
    }
    // ... 新增支付方式继续加 if-else
    return "支付方式不支持";
}

熟悉吗?这就是典型的 if-else 地狱

  • 新增支付方式?修改原有代码,违反开闭原则
  • 代码越来越长?维护成本直线上升
  • 单元测试难?每个分支都要测一遍

有没有更好的写法?

有!今天要讲的 策略模式(Strategy Pattern) ,就是专门用来拯救这类代码的「救星」。

读完这篇文章,你将掌握:

  • ✅ 什么是策略模式,它解决了什么问题
  • ✅ 如何从零实现一个策略模式
  • ✅ 实战场景:用策略模式重构支付系统
  • ✅ 进阶技巧:与 Spring 框架结合使用

预计阅读时间:8 分钟


一、什么是策略模式?

1.1 核心定义

策略模式:定义一系列算法,把它们封装起来,并且使它们可以互相替换。

一句话概括:策略模式是 if-else 的终极终结者。

它的核心思想是:

  • 将「做什么」和「谁来做」分离
  • 不同的算法(策略)可以互换
  • 客户端选择使用哪个策略

1.2 三大组件

策略模式由三个核心角色组成:

角色职责代码体现
抽象策略定义策略的公共接口接口或抽象类
具体策略实现具体的算法逻辑实现类
上下文持有策略引用,调用策略方法Client/Context

1.3 类图结构

┌─────────────────┐
│  Context        │
│  ─────────────  │
│  - strategy     │──────────┐
│  ─────────────  │          │
│  + execute()    │          │
└─────────────────┘          │
                             │
                    ┌────────▼─────────┐
                    │  <<interface>>   │
                    │  Strategy        │
                    │  ────────────    │
                    │  + execute()     │
                    └────────▲─────────┘
                             │
           ┌─────────────────┼─────────────────┐
           │                 │                 │
┌──────────┴─────┐ ┌─────────┴─────┐ ┌────────┴──────┐
│ StrategyA      │ │ StrategyB     │ │ StrategyC     │
│ ───────────    │ │ ───────────   │ │ ──────────    │
│ + execute()    │ │ + execute()   │ │ + execute()   │
└────────────────┘ └───────────────┘ └───────────────┘

二、从 0 到 1 实现策略模式

让我们用一个简单例子来理解策略模式的实现过程。

2.1 场景设定

假设我们需要实现一个「折扣计算」功能,不同会员等级享受不同折扣:

  • 普通会员:无折扣
  • 银牌会员:9 折
  • 金牌会员:8 折
  • 钻石会员:7 折

2.2 传统 if-else 写法

public class DiscountCalculator {
​
    public BigDecimal calculate(String memberLevel, BigDecimal price) {
        if ("normal".equals(memberLevel)) {
            return price; // 无折扣
        } else if ("silver".equals(memberLevel)) {
            return price.multiply(new BigDecimal("0.9")); // 9折
        } else if ("gold".equals(memberLevel)) {
            return price.multiply(new BigDecimal("0.8")); // 8折
        } else if ("diamond".equals(memberLevel)) {
            return price.multiply(new BigDecimal("0.7")); // 7折
        }
        return price;
    }
}

问题来了:新增「铂金会员」6 折怎么办?改代码,测一遍,上线...

2.3 策略模式重构

Step 1: 定义抽象策略

/**
 * 折扣策略接口
 */
public interface DiscountStrategy {
​
    /**
     * 计算折扣后的价格
     * @param originalPrice 原价
     * @return 折后价
     */
    BigDecimal calculate(BigDecimal originalPrice);
​
    /**
     * 获取策略类型标识
     */
    String getType();
}

Step 2: 实现具体策略

/**
 * 普通会员策略 - 无折扣
 */
public class NormalDiscountStrategy implements DiscountStrategy {
​
    @Override
    public BigDecimal calculate(BigDecimal originalPrice) {
        return originalPrice;
    }
​
    @Override
    public String getType() {
        return "normal";
    }
}
​
/**
 * 银牌会员策略 - 9折
 */
public class SilverDiscountStrategy implements DiscountStrategy {
​
    @Override
    public BigDecimal calculate(BigDecimal originalPrice) {
        return originalPrice.multiply(new BigDecimal("0.9"));
    }
​
    @Override
    public String getType() {
        return "silver";
    }
}
​
/**
 * 金牌会员策略 - 8折
 */
public class GoldDiscountStrategy implements DiscountStrategy {
​
    @Override
    public BigDecimal calculate(BigDecimal originalPrice) {
        return originalPrice.multiply(new BigDecimal("0.8"));
    }
​
    @Override
    public String getType() {
        return "gold";
    }
}

Step 3: 策略工厂/调度器

import java.util.HashMap;
import java.util.Map;
​
/**
 * 策略工厂 - 管理所有策略
 */
public class DiscountStrategyFactory {
​
    private static final Map<String, DiscountStrategy> STRATEGY_MAP = new HashMap<>();
​
    // 静态初始化,注册所有策略
    static {
        STRATEGY_MAP.put("normal", new NormalDiscountStrategy());
        STRATEGY_MAP.put("silver", new SilverDiscountStrategy());
        STRATEGY_MAP.put("gold", new GoldDiscountStrategy());
    }
​
    /**
     * 根据类型获取策略
     */
    public static DiscountStrategy getStrategy(String type) {
        DiscountStrategy strategy = STRATEGY_MAP.get(type);
        if (strategy == null) {
            throw new IllegalArgumentException("不支持的会员等级: " + type);
        }
        return strategy;
    }
​
    /**
     * 注册新策略(支持动态扩展)
     */
    public static void registerStrategy(String type, DiscountStrategy strategy) {
        STRATEGY_MAP.put(type, strategy);
    }
}

Step 4: 客户端调用

public class Client {
​
    public static void main(String[] args) {
        BigDecimal originalPrice = new BigDecimal("100");
​
        // 银牌会员支付
        DiscountStrategy silverStrategy = DiscountStrategyFactory.getStrategy("silver");
        BigDecimal silverPrice = silverStrategy.calculate(originalPrice);
        System.out.println("银牌会员价格: " + silverPrice); // 90.0
​
        // 金牌会员支付
        DiscountStrategy goldStrategy = DiscountStrategyFactory.getStrategy("gold");
        BigDecimal goldPrice = goldStrategy.calculate(originalPrice);
        System.out.println("金牌会员价格: " + goldPrice); // 80.0
    }
}

2.4 新增策略有多简单?

现在要新增「钻石会员 7 折」:

// 1. 新建策略类
public class DiamondDiscountStrategy implements DiscountStrategy {
​
    @Override
    public BigDecimal calculate(BigDecimal originalPrice) {
        return originalPrice.multiply(new BigDecimal("0.7"));
    }
​
    @Override
    public String getType() {
        return "diamond";
    }
}
​
// 2. 注册策略(在工厂类的 static 块中添加)
STRATEGY_MAP.put("diamond", new DiamondDiscountStrategy());
​
// 完成!无需修改任何现有代码

这就是开闭原则的魅力:对扩展开放,对修改关闭。


三、实战案例:支付系统重构

让我们用更真实的业务场景来巩固理解。

3.1 传统方案的问题

@Service
public class PaymentService {
​
    public String pay(String payType, BigDecimal amount) {
        if ("alipay".equals(payType)) {
            // 支付宝支付逻辑
            log.info("调用支付宝API,金额: {}", amount);
            return callAlipayApi(amount);
        } else if ("wechat".equals(payType)) {
            // 微信支付逻辑
            log.info("调用微信API,金额: {}", amount);
            return callWechatApi(amount);
        } else if ("unionpay".equals(payType)) {
            // 银联支付逻辑
            log.info("调用银联API,金额: {}", amount);
            return callUnionPayApi(amount);
        }
        throw new RuntimeException("不支持的支付方式");
    }
​
    // ... 各种私有方法
}

痛点

  • 新增支付方式要改代码、重新测试、重新部署
  • 单个方法越来越长,可读性差
  • 违反单一职责原则

3.2 策略模式重构方案

第一步:定义支付策略接口

/**
 * 支付策略接口
 */
public interface PaymentStrategy {
​
    /**
     * 执行支付
     * @param amount 支付金额
     * @return 支付结果
     */
    PaymentResult pay(BigDecimal amount);
​
    /**
     * 获取支付方式标识
     */
    String getPayType();
}

第二步:实现具体支付策略

/**
 * 支付宝支付策略
 */
@Component
public class AlipayStrategy implements PaymentStrategy {
​
    @Autowired
    private AlipayClient alipayClient;
​
    @Override
    public PaymentResult pay(BigDecimal amount) {
        log.info("调用支付宝支付,金额: {}", amount);
        // 支付宝特定逻辑
        AlipayRequest request = buildAlipayRequest(amount);
        AlipayResponse response = alipayClient.execute(request);
        return convertToPaymentResult(response);
    }
​
    @Override
    public String getPayType() {
        return "alipay";
    }
}
​
/**
 * 微信支付策略
 */
@Component
public class WechatPayStrategy implements PaymentStrategy {
​
    @Autowired
    private WechatPayClient wechatPayClient;
​
    @Override
    public PaymentResult pay(BigDecimal amount) {
        log.info("调用微信支付,金额: {}", amount);
        // 微信特定逻辑
        WechatPayRequest request = buildWechatRequest(amount);
        WechatPayResponse response = wechatPayClient.execute(request);
        return convertToPaymentResult(response);
    }
​
    @Override
    public String getPayType() {
        return "wechat";
    }
}

第三步:策略工厂(利用 Spring 自动注入)

@Component
public class PaymentStrategyFactory {
​
    private final Map<String, PaymentStrategy> strategyMap;
​
    /**
     * Spring 自动注入所有 PaymentStrategy 实现类
     */
    @Autowired
    public PaymentStrategyFactory(List<PaymentStrategy> strategies) {
        this.strategyMap = strategies.stream()
            .collect(Collectors.toMap(
                PaymentStrategy::getPayType,
                Function.identity()
            ));
    }
​
    /**
     * 根据支付类型获取策略
     */
    public PaymentStrategy getStrategy(String payType) {
        PaymentStrategy strategy = strategyMap.get(payType);
        if (strategy == null) {
            throw new IllegalArgumentException("不支持的支付方式: " + payType);
        }
        return strategy;
    }
}

第四步:重构支付服务

@Service
public class PaymentService {
​
    @Autowired
    private PaymentStrategyFactory strategyFactory;
​
    /**
     * 支付方法 - 简洁!
     */
    public String pay(String payType, BigDecimal amount) {
        PaymentStrategy strategy = strategyFactory.getStrategy(payType);
        PaymentResult result = strategy.pay(amount);
        return result.getOrderId();
    }
}

3.3 新增支付方式

新增「京东支付」只需三步:

// 1. 实现策略接口
@Component
public class JDPayStrategy implements PaymentStrategy {
​
    @Override
    public PaymentResult pay(BigDecimal amount) {
        // 京东支付逻辑
        return ...;
    }
​
    @Override
    public String getPayType() {
        return "jd";
    }
}
​
// 2. Spring 自动扫描并注入(无需修改工厂类)
// 3. 完毕!可以直接使用了

四、进阶技巧

4.1 策略模式 + 工厂模式

上文的 DiscountStrategyFactory 就是典型的组合使用:

  • 策略模式:封装不同的算法
  • 工厂模式:管理策略的创建和获取

4.2 策略模式 + Spring 容器

利用 Spring 的依赖注入,可以自动注册策略:

@Component
public class PaymentStrategyFactory {
​
    private final Map<String, PaymentStrategy> strategyMap = new ConcurrentHashMap<>();
​
    /**
     * 自动注册所有策略
     */
    @Autowired
    public void registerStrategies(List<PaymentStrategy> strategies) {
        strategies.forEach(strategy ->
            strategyMap.put(strategy.getPayType(), strategy)
        );
    }
​
    public PaymentStrategy getStrategy(String payType) {
        return strategyMap.get(payType);
    }
}

优势

  • 新增策略类后,Spring 自动扫描并注入
  • 无需手动维护注册代码
  • 符合开闭原则

4.3 策略模式 + 枚举

对于简单场景,可以用枚举实现策略:

public enum DiscountStrategyEnum {
​
    NORMAL(1, "normal", price -> price),
    SILVER(2, "silver", price -> price.multiply(new BigDecimal("0.9"))),
    GOLD(3, "gold", price -> price.multiply(new BigDecimal("0.8")));
​
    private final int code;
    private final String type;
    private final Function<BigDecimal, BigDecimal> calculator;
​
    DiscountStrategyEnum(int code, String type, Function<BigDecimal, BigDecimal> calculator) {
        this.code = code;
        this.type = type;
        this.calculator = calculator;
    }
​
    public BigDecimal calculate(BigDecimal price) {
        return calculator.apply(price);
    }
​
    public static DiscountStrategyEnum fromType(String type) {
        return Arrays.stream(values())
            .filter(e -> e.type.equals(type))
            .findFirst()
            .orElseThrow(() -> new IllegalArgumentException("Unknown type: " + type));
    }
}
​
// 使用
BigDecimal price = new BigDecimal("100");
BigDecimal result = DiscountStrategyEnum.fromType("gold").calculate(price);

五、什么时候用策略模式?

✅ 适用场景

场景示例
多个算法只有细微差别不同排序算法、压缩算法
需要动态切换算法根据用户等级选择折扣策略
算法需要频繁扩展支付方式、物流方式
想隐藏算法实现细节加密算法、缓存策略

❌ 不适用场景

  • 算法只有一种,且不会扩展
  • 算法极其简单,if-else 就够用
  • 客户端不需要知道算法的存在

⚠️ 注意事项

  1. 避免过度设计:如果只有 2-3 个分支且不会扩展,if-else 更简单
  2. 策略数量控制:策略过多时考虑组合模式或其他模式
  3. 策略选择逻辑:复杂的选择逻辑可考虑责任链模式

六、策略模式的优缺点

✅ 优点

  1. 符合开闭原则:新增策略无需修改原有代码
  2. 符合单一职责:每个策略只负责一种算法
  3. 提高代码可读性:代码结构清晰,职责分明
  4. 便于单元测试:每个策略独立测试

❌ 缺点

  1. 类数量增加:每个策略一个类
  2. 客户端必须知道策略:需要选择使用哪个策略
  3. 学习成本:新手需要理解设计模式

七、总结

一个公式总结:

策略模式 = 抽象策略接口 + 具体策略实现 + 策略工厂管理

核心价值

  • 🎯 消灭 if-else 地狱:用多态替代条件判断
  • 🔧 符合开闭原则:新增功能不修改原有代码
  • 📦 职责单一:每个策略独立、可测试

记住这三个场景,直接上策略模式

  1. 同一功能有多种实现方式
  2. 需要动态切换算法
  3. 算法会频繁扩展