🔔 本文 5000+ 字深度原创,含完整代码示例和生产级落地方案。创作不易,如果对你有帮助,请点赞 👍 收藏 ⭐ 关注 🔥 三连支持,你的认可是我持续输出的最大动力!
本文是「设计模式实战解读」系列第五篇。系列文章统一按照 定义 → 痛点场景 → 模式结构 → 核心实现 → 真实应用 → 常见变种 → 优缺点 → 避坑指南 → FAQ 的结构展开,每篇聚焦一个模式讲透。
一句话定义
策略模式(Strategy):定义一族算法,把每个算法封装成独立的类,使它们可以互相替换。调用方选择使用哪种策略,不关心策略的内部实现。
归属:行为型模式。
一、没有策略模式时的痛点
假设你在做一个支付模块,需要支持多种支付方式:
java
public class PaymentService {
public PayResult pay(PayRequest request) {
if ("wechat".equals(request.getPayType())) {
// 微信支付逻辑(20 行)
String prepayId = wechatClient.createOrder(...);
return buildWechatResult(prepayId);
} else if ("alipay".equals(request.getPayType())) {
// 支付宝逻辑(25 行)
String tradeNo = alipayClient.tradeCreate(...);
return buildAlipayResult(tradeNo);
} else if ("unionpay".equals(request.getPayType())) {
// 银联逻辑(30 行)
String tn = unionPayClient.frontTransReq(...);
return buildUnionPayResult(tn);
} else if ("balance".equals(request.getPayType())) {
// 余额支付逻辑(15 行)
deductBalance(request.getUserId(), request.getAmount());
return buildBalanceResult();
}
// 随着支付方式增加,这个方法会一直膨胀...
throw new BizException("Unsupported pay type");
}
}
这段代码的问题:
- 违反开闭原则:每增加一种支付方式,都要修改这个方法
- 方法越来越长:4 种支付方式已经 90+ 行,10 种就是 200+ 行
- 难以测试:测试某种支付方式,要绕过所有其他分支
- 无法复用:微信支付的逻辑散在 if 块里,其他地方想复用也难以抽取
- 维护风险高:修改微信支付逻辑时,可能误改 if-else 结构影响其他支付方式
这就是典型的"分支爆炸"------当业务逻辑按照某个**维度(支付类型、折扣类型、导出格式...)**有多种差异化实现时,if-else 就会失控。
二、模式结构
┌──────────────────────────────┐
│ Context(上下文) │
├──────────────────────────────┤
│ - strategy: PayStrategy │ ← 持有策略引用
├──────────────────────────────┤
│ + setStrategy(PayStrategy) │
│ + executeStrategy(request) │ ← 委托给策略执行
└──────────────────────────────┘
│ 依赖
↓
┌──────────────────────────────┐
│ PayStrategy(接口) │
├──────────────────────────────┤
│ + pay(PayRequest): PayResult │ ← 统一的策略接口
└──────────────────────────────┘
↑ ↑ ↑
WechatPay Alipay UnionPay ← 具体策略实现
三个角色:
- Strategy(策略接口):定义算法的统一签名
- ConcreteStrategy(具体策略):每种算法的具体实现
- Context(上下文):持有策略引用,对外提供调用入口
关键点:Context 不知道具体用了哪个策略,策略对 Context 透明。
三、核心实现
3.1 基础版:接口 + 实现类
java
// 策略接口
public interface PayStrategy {
PayResult pay(PayRequest request);
// 声明自己支持哪种支付类型(用于策略选择)
String payType();
}
// 具体策略:微信支付
@Component
public class WechatPayStrategy implements PayStrategy {
@Override
public String payType() {
return "wechat";
}
@Override
public PayResult pay(PayRequest request) {
// 微信支付完整逻辑,独立封装,互不干扰
String prepayId = wechatClient.createOrder(
request.getAmount(), request.getOutTradeNo(), request.getNotifyUrl()
);
return PayResult.success(prepayId);
}
}
// 具体策略:支付宝
@Component
public class AlipayStrategy implements PayStrategy {
@Override
public String payType() {
return "alipay";
}
@Override
public PayResult pay(PayRequest request) {
String tradeNo = alipayClient.tradeCreate(...);
return PayResult.success(tradeNo);
}
}
3.2 Spring 自动注册(推荐)
java
// 策略上下文:自动收集所有策略实现
@Component
public class PayContext {
// Spring 把所有 PayStrategy 实现按 Bean Name 注入
private final Map<String, PayStrategy> strategyMap;
public PayContext(List<PayStrategy> strategies) {
this.strategyMap = strategies.stream()
.collect(Collectors.toMap(PayStrategy::payType, Function.identity()));
}
public PayResult pay(PayRequest request) {
PayStrategy strategy = strategyMap.get(request.getPayType());
if (strategy == null) {
throw new BizException(ErrorCode.PAY_TYPE_NOT_SUPPORTED,
"Unsupported pay type: " + request.getPayType());
}
return strategy.pay(request);
}
}
改造后的 PaymentService:
java
@Service
public class PaymentService {
@Autowired
private PayContext payContext;
public PayResult pay(PayRequest request) {
// 原来 90 行变成 1 行
return payContext.pay(request);
}
}
新增一种支付方式(比如Apple Pay),只需要:
java
@Component
public class ApplePayStrategy implements PayStrategy {
@Override
public String payType() { return "applepay"; }
@Override
public PayResult pay(PayRequest request) { /* ... */ }
}
一行代码都不需要修改。 这就是策略模式的核心价值:让扩展变成"加"而不是"改"。
3.3 函数式策略(Lambda)
当策略逻辑简单时,可以用 Lambda 代替独立类:
java
// 策略接口本身就是函数式接口
@FunctionalInterface
public interface DiscountStrategy {
BigDecimal calculate(BigDecimal originalPrice, int quantity);
}
// 用 Lambda 定义策略,不需要单独建类
public class DiscountStrategyRegistry {
private static final Map<String, DiscountStrategy> REGISTRY = new HashMap<>();
static {
// 九折优惠
REGISTRY.put("MEMBER_DISCOUNT", (price, qty) -> price.multiply(new BigDecimal("0.9")));
// 满100减20
REGISTRY.put("FULL_REDUCTION", (price, qty) -> {
if (price.compareTo(new BigDecimal("100")) >= 0) {
return price.subtract(new BigDecimal("20"));
}
return price;
});
// 阶梯折扣
REGISTRY.put("LADDER_DISCOUNT", (price, qty) -> {
if (qty >= 10) return price.multiply(new BigDecimal("0.8"));
if (qty >= 5) return price.multiply(new BigDecimal("0.85"));
return price;
});
}
public static BigDecimal calculate(String discountCode, BigDecimal price, int qty) {
DiscountStrategy strategy = REGISTRY.getOrDefault(discountCode,
(p, q) -> p); // 默认不打折
return strategy.calculate(price, qty);
}
}
四、真实应用场景
4.1 框架级应用
| 框架 | 策略接口 | 具体策略 | 策略选择依据 |
|---|---|---|---|
| Spring Security | AuthenticationProvider | DaoAuthenticationProvider / OAuthProvider | 认证类型 |
| Spring MVC | HandlerAdapter | RequestMappingHandlerAdapter / SimpleControllerHandlerAdapter | Controller 类型 |
| MyBatis | TypeHandler | StringTypeHandler / IntegerTypeHandler | Java/JDBC 类型 |
| Java 排序 | Comparator | 各种比较器 | 排序字段 |
| Spring Retry | RetryPolicy | SimpleRetryPolicy / ExceptionClassifierRetryPolicy | 重试条件 |
4.2 业务场景
| 业务 | 策略维度 | 典型策略 | 选择依据 |
|---|---|---|---|
| 支付 | 支付渠道 | 微信/支付宝/银联/余额 | 用户选择 |
| 折扣计算 | 优惠类型 | 会员折扣/满减/阶梯折扣 | 活动配置 |
| 消息通知 | 通知渠道 | 邮件/短信/钉钉/企微 | 用户偏好 |
| 文件导出 | 导出格式 | Excel/CSV/PDF/HTML | 用户选择 |
| 风控 | 风险等级 | 拒绝/人工审核/放行 | 风险分数 |
| 数据同步 | 同步模式 | 全量/增量/差量 | 数据量和时效 |
4.3 iPaaS 连接器鉴权策略
在 iPaaS 平台中,不同连接器的鉴权方式完全不同------这是策略模式的天然场景:
java
// 鉴权策略接口
public interface AuthStrategy {
void auth(HttpRequest request, ConnectorConfig config);
AuthType supportedAuthType();
}
// OAuth2 鉴权策略
@Component
public class OAuth2AuthStrategy implements AuthStrategy {
@Override
public AuthType supportedAuthType() { return AuthType.OAUTH2; }
@Override
public void auth(HttpRequest request, ConnectorConfig config) {
String token = tokenCache.getOrRefresh(config.getConnectorId());
request.addHeader("Authorization", "Bearer " + token);
}
}
// API Key 鉴权策略
@Component
public class ApiKeyAuthStrategy implements AuthStrategy {
@Override
public AuthType supportedAuthType() { return AuthType.API_KEY; }
@Override
public void auth(HttpRequest request, ConnectorConfig config) {
request.addHeader(config.getKeyHeader(), config.getApiKey());
}
}
// Basic Auth 鉴权策略
@Component
public class BasicAuthStrategy implements AuthStrategy {
@Override
public AuthType supportedAuthType() { return AuthType.BASIC; }
@Override
public void auth(HttpRequest request, ConnectorConfig config) {
String credentials = Base64.encode(config.getUsername() + ":" + config.getPassword());
request.addHeader("Authorization", "Basic " + credentials);
}
}
连接器执行器中:
java
// 自动根据连接器配置的鉴权类型选择策略
authStrategyMap.get(config.getAuthType()).auth(request, config);
新增一种鉴权方式(比如 HMAC 签名),加一个 HmacAuthStrategy 类即可,连接器执行器不需要改动一行。
五、常见变种
5.1 策略 + 工厂
当策略创建本身有复杂逻辑时(需要初始化状态、依赖配置),用工厂来创建策略:
java
public class PayStrategyFactory {
public PayStrategy create(String payType, PayConfig config) {
return switch (payType) {
case "wechat" -> new WechatPayStrategy(config.getWechatAppId(), config.getMchId());
case "alipay" -> new AlipayStrategy(config.getAlipayAppId(), config.getPrivateKey());
default -> throw new BizException("Unsupported: " + payType);
};
}
}
适用场景:策略实例是有状态的、需要按配置初始化,不适合作为 Spring 单例的情况。
5.2 策略链(责任链的变种)
多个策略按顺序执行,直到某个策略处理成功:
java
public class FallbackPayContext {
private final List<PayStrategy> chain; // 按优先级排序
public PayResult pay(PayRequest request) {
for (PayStrategy strategy : chain) {
if (strategy.supports(request)) {
try {
return strategy.pay(request);
} catch (PayException e) {
log.warn("Strategy {} failed, trying next", strategy.payType(), e);
}
}
}
throw new BizException("All strategies failed");
}
}
5.3 枚举策略
当策略种类有限且固定时,用枚举简洁实现:
java
public enum ShippingStrategy {
STANDARD {
@Override
public BigDecimal calculateFee(int weight, int distance) {
return new BigDecimal("5").add(new BigDecimal(weight).multiply(new BigDecimal("0.01")));
}
},
EXPRESS {
@Override
public BigDecimal calculateFee(int weight, int distance) {
return new BigDecimal("15").add(new BigDecimal(weight).multiply(new BigDecimal("0.02")));
}
};
public abstract BigDecimal calculateFee(int weight, int distance);
}
// 使用
BigDecimal fee = ShippingStrategy.EXPRESS.calculateFee(500, 100);
六、优缺点
| 优点 | 缺点 |
|---|---|
| 消灭 if-else,逻辑清晰 | 策略类数量增加 |
| 符合开闭原则(新增策略不改原代码) | 调用方需要知道有哪些策略可选 |
| 每个策略独立,方便单测 | 简单场景下引入策略模式过度设计 |
| 策略可以动态切换 | 策略之间如果有共享状态,需要额外处理 |
| 支持组合(策略链、策略 + 工厂) | 策略选择逻辑本身可能变成新的 if-else |
七、避坑指南
坑 1:策略本身有状态(并发问题)
策略通常是 Spring 单例(@Component),如果策略类里有成员变量存储"当前请求的状态",多线程并发时会乱。
原则:策略类必须无状态------请求相关的状态通过参数传入,不存在策略类的成员变量里。
java
// ❌ 有状态的策略(并发不安全)
@Component
public class WechatPayStrategy implements PayStrategy {
private PayRequest currentRequest; // 危险!多线程会乱
public PayResult pay(PayRequest request) {
this.currentRequest = request; // 并发问题
...
}
}
// ✓ 无状态策略(正确)
@Component
public class WechatPayStrategy implements PayStrategy {
public PayResult pay(PayRequest request) {
// request 是方法参数,线程安全
...
}
}
坑 2:策略选择逻辑本身又变成 if-else
java
// ❌ 用 if-else 选择策略,治好了一个 if-else 又来一个
String strategy;
if (order.isMember()) {
strategy = "MEMBER_DISCOUNT";
} else if (order.getTotal().compareTo(new BigDecimal("100")) >= 0) {
strategy = "FULL_REDUCTION";
} else {
strategy = "NO_DISCOUNT";
}
解法 :策略接口加一个 supports(context) 方法,让策略自己说明适用条件:
java
public interface DiscountStrategy {
boolean supports(OrderContext context); // 策略自己声明适用范围
BigDecimal calculate(OrderContext context);
}
// 策略选择变成遍历
strategies.stream()
.filter(s -> s.supports(context))
.findFirst()
.orElse(noDiscountStrategy)
.calculate(context);
坑 3:Context 对象膨胀
随着业务增长,传入策略的 Context 参数越来越大,变成一个包含所有可能字段的"上帝对象"。
解法:为不同策略族定义不同的 Context 接口,策略只接受它需要的最小上下文。
坑 4:过度使用策略模式
两三个 if-else 不需要上策略模式。策略模式的适用信号:
- 分支数量 ≥ 5 且还会继续增加
- 每个分支的逻辑 ≥ 10 行
- 有明确的扩展需求(新渠道、新格式、新算法)
两三个简单分支直接写 if-else 更清晰。
八、常见问题(FAQ)
Q:策略模式和工厂模式有什么区别?
A:工厂模式关注的是"创建什么对象 ",策略模式关注的是"用什么算法执行"。实际项目中经常结合:工厂负责创建策略对象,Context 负责调用策略。
Q:策略模式和状态模式很像,怎么区分?
A:策略模式中,策略是外部传入的 (调用方决定用哪个策略,策略本身不会互相转换)。状态模式中,状态是对象内部的(对象根据当前状态决定行为,状态之间可以互相转换)。简单说:策略是"选择",状态是"转换"。
Q:Spring 里直接用多个 if-else 分支调用不同的 @Service,和策略模式有什么实质区别?
A:如果你的 if-else 分支没有抽象出统一接口,只是调用不同的 Service 方法,那就只是"用 Spring 管理依赖的 if-else",不是策略模式。策略模式的核心是统一接口------所有策略对 Context 表现一致,Context 不知道具体哪个实现被调用。
Q:什么时候该用枚举策略,什么时候该用独立类策略?
A:策略数量固定 且不会动态扩展 (不需要第三方插件新增策略)→ 枚举;策略可能动态扩展(新加策略不改代码)→ 独立类 + Spring 自动注入。
九、小结
策略模式的核心价值:把"用什么算法"的决策和"如何执行算法"的实现解耦。
三个实践要点:
- Spring 项目用
List<Strategy>自动注入 + Map 路由,零手动注册,新增策略一键生效 - 策略类必须无状态------成员变量不存运行时数据,请求上下文通过参数传递
- 策略接口加
supports()方法------让策略自我声明适用范围,消灭策略选择层的 if-else
下一篇我们聊装饰器模式------当你需要给一个对象添加新功能,但又不想修改原始类、也不想用继承时,如何优雅地"包装"它。
标签:#设计模式 #策略模式 #Strategy #行为型模式 #Java #Spring #消灭if-else #函数式编程 #支付策略 #连接器鉴权 #面向对象 #软件工程