前言:你有没有写过这样的代码?
先来看一段「经典」代码:
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 就够用
- 客户端不需要知道算法的存在
⚠️ 注意事项
- 避免过度设计:如果只有 2-3 个分支且不会扩展,if-else 更简单
- 策略数量控制:策略过多时考虑组合模式或其他模式
- 策略选择逻辑:复杂的选择逻辑可考虑责任链模式
六、策略模式的优缺点
✅ 优点
- 符合开闭原则:新增策略无需修改原有代码
- 符合单一职责:每个策略只负责一种算法
- 提高代码可读性:代码结构清晰,职责分明
- 便于单元测试:每个策略独立测试
❌ 缺点
- 类数量增加:每个策略一个类
- 客户端必须知道策略:需要选择使用哪个策略
- 学习成本:新手需要理解设计模式
七、总结
一个公式总结:
策略模式 = 抽象策略接口 + 具体策略实现 + 策略工厂管理
核心价值:
- 🎯 消灭 if-else 地狱:用多态替代条件判断
- 🔧 符合开闭原则:新增功能不修改原有代码
- 📦 职责单一:每个策略独立、可测试
记住这三个场景,直接上策略模式:
- 同一功能有多种实现方式
- 需要动态切换算法
- 算法会频繁扩展