📌 PDF :AI人工智能 --- AI Agent智能体实战项目
大白话说Java设计模式-26-策略模式(业务实战篇):大白商城支付策略的"算法切换"
📌 一句话本质:策略模式就是"可替换的算法",让算法独立于使用它的客户。
🏷️ 标签:策略模式 / Java 设计模式 / 算法切换 / 支付策略 / 大白商城 🎯 适合:初中级后端 / 经常被"多种算法"困扰的工程师
目录
- 一、业务场景引入:为什么需要"可切换的算法"?
- 二、反面教材:硬编码算法的"灾难现场"
- [三、模式原理:策略的"算法族 + 上下文"](#三、模式原理:策略的"算法族 + 上下文")
- 四、实战代码:大白商城支付策略完整实现
- [五、策略 vs 工厂方法 / 状态模式](#五、策略 vs 工厂方法 / 状态模式)
- [六、Spring 的 ResourceLoader 怎么借鉴策略?](#六、Spring 的 ResourceLoader 怎么借鉴策略?)
- [七、策略 + 工厂方法怎么组合?](#七、策略 + 工厂方法怎么组合?)
- [八、工程决策 Checklist](#八、工程决策 Checklist)
- 九、与其他模式协作
- [十、本篇小结 + 下篇预告](#十、本篇小结 + 下篇预告)
一、业务场景引入:为什么需要"可切换的算法"?
大白商城 2024 年对接 5 家支付渠道,踩了一个大坑:
"用户下单时要选支付方式(支付宝/微信/银联/数字人民币),不同支付方式逻辑不一样,if-else 写到 800 行!"
我打开代码一看:
java
/**
* ❌ 大白商城老的支付代码
*/
public class PaymentServiceV0 {
public PaymentResult pay(String channel, BigDecimal amount) {
if ("alipay".equals(channel)) {
// 50 行支付宝逻辑
return alipayPay(amount);
} else if ("wechat".equals(channel)) {
// 50 行微信逻辑
return wechatPay(amount);
} else if ("unionpay".equals(channel)) {
// 50 行银联逻辑
return unionPay(amount);
} else if ("digital_rmb".equals(channel)) {
// 50 行数字人民币逻辑
return digitalRMBPay(amount);
}
throw new IllegalArgumentException("未知支付方式: " + channel);
}
}
这种代码的痛点:
| 序号 | 问题 | 后果 |
|---|---|---|
| ① | if-else 堆叠 | 800 行难维护 |
| ② | 加新渠道要改老代码 | 改一处牵动全身 |
| ③ | 每种支付耦合 | 测试要测整个方法 |
| ④ | 算法逻辑分散 | 业务被淹没 |
老板要的"加新支付方式不动老代码",怎么搞?
答案就是------策略模式。
1.1 大白话讲透策略
继续打比方:
场景:大白商城有 4 种支付方式(支付宝/微信/银联/数字人民币)。每种支付的"如何扣款"逻辑不一样。
- 错误做法:所有逻辑写在一个 if-else 里
- 正确做法 :
- 把每种支付封装成独立策略 :
AlipayStrategy/WechatStrategy/UnionPayStrategy/DigitalRmbStrategy- 业务方只调"支付上下文":
PaymentContext- 上下文根据渠道自动选择对应策略
- 加新支付?新建一个 Strategy 类,老代码不动
策略模式 = 定义一系列算法,封装每个算法,让它们可以互相替换。
1.2 策略模式的 3 个真实场景
大白商城里,策略模式用在:
| 场景 | "算法族" |
|---|---|
| 支付方式 | 支付宝/微信/银联/数字人民币 |
| 优惠策略 | 满减/折扣/立减/优惠券 |
| 物流方式 | 标准/加急/次日达 |
| 排序方式 | 按价格/按销量/按时间 |
| 消息发送 | 短信/邮件/推送 |
任何"多种算法可切换"的场景,都用策略。
二、反面教材:硬编码算法的"灾难现场"
我们看 4 个反面教材,看它们是怎么一步步崩的。
2.1 反面教材 v1:if-else 堆叠
java
/**
* ❌ 反面教材 v1:if-else 堆叠
*/
public class PaymentServiceV1 {
public PaymentResult pay(String channel, BigDecimal amount) {
if ("alipay".equals(channel)) {
return alipayPay(amount);
} else if ("wechat".equals(channel)) {
return wechatPay(amount);
}
// ... 800 行
}
}
翻车现场:
| 序号 | 问题 | 后果 |
|---|---|---|
| ① | 代码臃肿 | 800 行 |
| ② | 加新渠道改老代码 | 改一处牵动全身 |
2.2 反面教材 v2:抽象基类 + 继承
java
/**
* ❌ 反面教材 v2:抽象基类
*/
public abstract class PaymentBase {
public abstract PaymentResult pay(BigDecimal amount);
}
public class AlipayService extends PaymentBase { ... }
public class WechatService extends PaymentBase { ... }
// 业务方
public class OrderServiceV2 {
public void pay(String channel, BigDecimal amount) {
PaymentBase payment;
if ("alipay".equals(channel)) {
payment = new AlipayService();
} else {
// ...
}
payment.pay(amount);
}
}
翻车现场:
| 序号 | 问题 | 后果 |
|---|---|---|
| ① | 仍有 if-else | 业务方要知道每种支付 |
| ② | 无法统一管理 | 业务方每次 new |
2.3 反面教材 v3:Map 存策略
java
/**
* ❌ 反面教材 v3:Map 存策略
*/
public class PaymentServiceV3 {
private static final Map<String, PaymentBase> STRATEGY_MAP = new HashMap<>();
static {
STRATEGY_MAP.put("alipay", new AlipayService());
STRATEGY_MAP.put("wechat", new WechatService());
}
public PaymentResult pay(String channel, BigDecimal amount) {
PaymentBase payment = STRATEGY_MAP.get(channel);
return payment.pay(amount);
}
}
翻车现场:
| 序号 | 问题 | 后果 |
|---|---|---|
| ① | 静态 Map 改不动 | 加新支付要改类 |
| ② | Map Key 易错 | 字符串写错就崩 |
2.4 反面教材 v4:完全分散
java
/**
* ❌ 反面教材 v4:每个 Controller 各自实现
*/
public class CartController {
public void pay() {
// 自己的支付逻辑
}
}
public class SeckillController {
public void pay() {
// 重复的支付逻辑
}
}
翻车现场:
| 序号 | 问题 | 后果 |
|---|---|---|
| ① | 重复代码 | 每个 Controller 都要写 |
| ② | 加新支付要全改 | 改 N 个 Controller |
2.5 4 个反面教材的共同病根
| 痛点 | 反模式方案能不能解决? |
|---|---|
| 加新算法不动老代码 | ❌ 全部要改 |
| 算法独立测试 | ❌ 全部耦合 |
| 业务方不关心算法细节 | ❌ 全部要懂 |
| 运行时切换 | ❌ 全部写死 |
必须上策略模式。
三、模式原理:策略的"算法族 + 上下文"
3.1 策略的 3 个核心角色
| 角色 | 职责 | 例子 |
|---|---|---|
| 抽象策略(Strategy) | 定义算法族接口 | PaymentStrategy |
| 具体策略(Concrete Strategy) | 实现具体算法 | AlipayStrategy / WechatStrategy |
| 上下文(Context) | 持有策略引用,调用算法 | PaymentContext |
关键点 :上下文持有策略引用 ,运行时切换。
3.2 一张图看懂策略
客户端(OrderService)
↓ 调用
PaymentContext(上下文)
│ 持有
├── PaymentStrategy strategy
│
├── AlipayStrategy
├── WechatStrategy
├── UnionPayStrategy
└── DigitalRmbStrategy
3.3 策略的"灵魂三问"
Q1:策略 vs 工厂方法,区别是什么?
答:
- 工厂方法 :创建对象(按类型创建)
- 策略 :执行算法(按类型执行不同逻辑)
- 工厂方法关注"造什么",策略关注"怎么做"
- 工厂返回对象,策略返回结果
Q2:策略 vs 状态模式,区别是什么?
答:
- 策略 :算法独立(之间无状态转换)
- 状态 :状态转换(之间有状态机)
- 支付方式之间没有状态转换(选支付宝不能变成微信)→ 策略
- 订单状态之间有转换(待支付→已支付→已发货)→ 状态
Q3:策略 vs if-else,怎么选?
答:
- 1-2 个分支 → if-else 够用
- 3+ 个分支 + 经常加 → 策略
- 每个分支有独立复杂逻辑 → 策略
- 业务简单 → if-else 更直接
3.4 策略的 3 种实现方式
| 方式 | 特点 | 适用 |
|---|---|---|
| 简单策略 | 客户端直接 new | 简单场景 |
| Spring 自动注入 | List<Strategy> 自动收集 |
多策略 |
| Map 工厂策略 | Map 缓存 + 自动选 | 高频调用 |
大白商城主推 Spring 自动注入。
四、实战代码:大白商城支付策略完整实现
下面是大白商城生产环境在用的策略实现,全套代码可直接复制到 IDEA 跑。
4.1 项目环境与依赖
pom.xml:
xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
<relativePath/>
</parent>
<groupId>com.dabai.mall</groupId>
<artifactId>mall-design-pattern-26</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>mall-design-pattern-26</name>
<description>大白商城 - 设计模式 26 策略模式</description>
<properties>
<java.version>17</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
4.2 抽象策略:PaymentStrategy
java
package com.dabai.mall.payment;
/**
* ✅ 抽象策略:支付策略
* <p>
* 所有支付渠道都实现这个接口
*
* @author 大白商城技术团队
*/
public interface PaymentStrategy {
/**
* ✅ 支付算法
*/
PaymentResult pay(PaymentRequest request);
/**
* ✅ 退款算法
*/
PaymentResult refund(String outTradeNo, java.math.BigDecimal amount);
/**
* ✅ 获取支持的支付渠道
*/
String getChannel();
}
配套 DTO:
java
package com.dabai.mall.payment;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PaymentRequest {
private String outTradeNo;
private BigDecimal amount;
private String subject;
private String userId;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
class PaymentResult {
private boolean success;
private String tradeNo;
private String outTradeNo;
private String errorCode;
private String errorMessage;
}
4.3 具体策略:4 种支付
AlipayStrategy:
java
package com.dabai.mall.payment.strategy;
import com.dabai.mall.payment.PaymentRequest;
import com.dabai.mall.payment.PaymentResult;
import com.dabai.mall.payment.PaymentStrategy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
/**
* ✅ 具体策略:支付宝
*/
@Slf4j
@Component
public class AlipayStrategy implements PaymentStrategy {
@Override
public PaymentResult pay(PaymentRequest request) {
log.info("【支付宝】发起支付, outTradeNo={}, amount={}",
request.getOutTradeNo(), request.getAmount());
// 实际调支付宝 SDK
return PaymentResult.builder()
.success(true)
.tradeNo("alipay_" + System.currentTimeMillis())
.outTradeNo(request.getOutTradeNo())
.build();
}
@Override
public PaymentResult refund(String outTradeNo, BigDecimal amount) {
log.info("【支付宝】退款, outTradeNo={}, amount={}", outTradeNo, amount);
return PaymentResult.builder()
.success(true)
.outTradeNo(outTradeNo)
.build();
}
@Override
public String getChannel() {
return "alipay";
}
}
WechatStrategy:
java
package com.dabai.mall.payment.strategy;
import com.dabai.mall.payment.PaymentRequest;
import com.dabai.mall.payment.PaymentResult;
import com.dabai.mall.payment.PaymentStrategy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
@Slf4j
@Component
public class WechatStrategy implements PaymentStrategy {
@Override
public PaymentResult pay(PaymentRequest request) {
log.info("【微信】发起支付, outTradeNo={}, amount={}",
request.getOutTradeNo(), request.getAmount());
return PaymentResult.builder()
.success(true)
.tradeNo("wechat_" + System.currentTimeMillis())
.outTradeNo(request.getOutTradeNo())
.build();
}
@Override
public PaymentResult refund(String outTradeNo, BigDecimal amount) {
log.info("【微信】退款, outTradeNo={}, amount={}", outTradeNo, amount);
return PaymentResult.builder().success(true).outTradeNo(outTradeNo).build();
}
@Override
public String getChannel() {
return "wechat";
}
}
UnionPayStrategy:
java
package com.dabai.mall.payment.strategy;
import com.dabai.mall.payment.PaymentRequest;
import com.dabai.mall.payment.PaymentResult;
import com.dabai.mall.payment.PaymentStrategy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
@Slf4j
@Component
public class UnionPayStrategy implements PaymentStrategy {
@Override
public PaymentResult pay(PaymentRequest request) {
log.info("【银联】发起支付, outTradeNo={}, amount={}",
request.getOutTradeNo(), request.getAmount());
return PaymentResult.builder()
.success(true)
.tradeNo("union_" + System.currentTimeMillis())
.outTradeNo(request.getOutTradeNo())
.build();
}
@Override
public PaymentResult refund(String outTradeNo, BigDecimal amount) {
log.info("【银联】退款, outTradeNo={}, amount={}", outTradeNo, amount);
return PaymentResult.builder().success(true).outTradeNo(outTradeNo).build();
}
@Override
public String getChannel() {
return "unionpay";
}
}
DigitalRmbStrategy:
java
package com.dabai.mall.payment.strategy;
import com.dabai.mall.payment.PaymentRequest;
import com.dabai.mall.payment.PaymentResult;
import com.dabai.mall.payment.PaymentStrategy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
@Slf4j
@Component
public class DigitalRmbStrategy implements PaymentStrategy {
@Override
public PaymentResult pay(PaymentRequest request) {
log.info("【数字人民币】发起支付, outTradeNo={}, amount={}",
request.getOutTradeNo(), request.getAmount());
return PaymentResult.builder()
.success(true)
.tradeNo("digital_rmb_" + System.currentTimeMillis())
.outTradeNo(request.getOutTradeNo())
.build();
}
@Override
public PaymentResult refund(String outTradeNo, BigDecimal amount) {
log.info("【数字人民币】退款, outTradeNo={}, amount={}", outTradeNo, amount);
return PaymentResult.builder().success(true).outTradeNo(outTradeNo).build();
}
@Override
public String getChannel() {
return "digital_rmb";
}
}
4.4 上下文:PaymentContext
java
package com.dabai.mall.payment;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* ✅ 上下文:支付上下文
* <p>
* 持有所有策略,根据 channel 自动选择
*
* @author 大白商城技术团队
*/
@Slf4j
@Service
public class PaymentContext {
/**
* 渠道 → 策略
*/
private final Map<String, PaymentStrategy> strategyMap = new HashMap<>();
/**
* ✅ Spring 自动注入所有 PaymentStrategy 的实现
*/
@Autowired
public PaymentContext(List<PaymentStrategy> strategies) {
for (PaymentStrategy strategy : strategies) {
strategyMap.put(strategy.getChannel(), strategy);
log.info("【PaymentContext】注册支付策略, channel={}, class={}",
strategy.getChannel(), strategy.getClass().getSimpleName());
}
}
/**
* ✅ 支付入口:根据 channel 自动选策略
*/
public PaymentResult pay(String channel, PaymentRequest request) {
PaymentStrategy strategy = strategyMap.get(channel);
if (strategy == null) {
throw new BusinessException("不支持的支付渠道: " + channel);
}
return strategy.pay(request);
}
/**
* 退款入口
*/
public PaymentResult refund(String channel, String outTradeNo, java.math.BigDecimal amount) {
PaymentStrategy strategy = strategyMap.get(channel);
if (strategy == null) {
throw new BusinessException("不支持的支付渠道: " + channel);
}
return strategy.refund(outTradeNo, amount);
}
}
/**
* 业务异常
*/
class BusinessException extends RuntimeException {
public BusinessException(String message) {
super(message);
}
}
4.5 业务调用方
java
package com.dabai.mall.payment;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
/**
* ✅ 订单支付:策略模式的调用方
* <p>
* 业务方只调 PaymentContext.pay(),不关心是哪种支付
*
* @author 大白商城技术团队
*/
@Slf4j
@Service
public class OrderPaymentService {
@Autowired
private PaymentContext paymentContext;
public PaymentResult pay(String channel, String orderNo, BigDecimal amount, String userId) {
PaymentRequest request = PaymentRequest.builder()
.outTradeNo(orderNo)
.amount(amount)
.subject("订单支付")
.userId(userId)
.build();
// ✅ 调上下文,传入 channel
return paymentContext.pay(channel, request);
}
}
关键点:
| 关键点 | 解释 |
|---|---|
业务方只调 PaymentContext.pay() |
不知道 4 种支付细节 |
| 加新支付? | 只加 Strategy 类 |
| 切换支付? | 改 channel 参数 |
4.6 单元测试
java
package com.dabai.mall.payment;
import com.dabai.mall.payment.strategy.*;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.math.BigDecimal;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
/**
* 策略模式完整单元测试
*/
class PaymentStrategyTest {
private PaymentContext paymentContext;
@BeforeEach
void setUp() {
// 注入 4 个策略
paymentContext = new PaymentContext(List.of(
new AlipayStrategy(),
new WechatStrategy(),
new UnionPayStrategy(),
new DigitalRmbStrategy()
));
}
@Test
void testAlipayPay() {
PaymentRequest request = PaymentRequest.builder()
.outTradeNo("ORDER_001")
.amount(new BigDecimal("100"))
.userId("USER_001")
.build();
PaymentResult result = paymentContext.pay("alipay", request);
assertTrue(result.isSuccess());
assertTrue(result.getTradeNo().startsWith("alipay_"));
}
@Test
void testWechatPay() {
PaymentRequest request = PaymentRequest.builder()
.outTradeNo("ORDER_002")
.amount(new BigDecimal("200"))
.build();
PaymentResult result = paymentContext.pay("wechat", request);
assertTrue(result.isSuccess());
assertTrue(result.getTradeNo().startsWith("wechat_"));
}
@Test
void testUnionPay() {
PaymentRequest request = PaymentRequest.builder()
.outTradeNo("ORDER_003")
.amount(new BigDecimal("300"))
.build();
PaymentResult result = paymentContext.pay("unionpay", request);
assertTrue(result.isSuccess());
}
@Test
void testDigitalRmb() {
PaymentRequest request = PaymentRequest.builder()
.outTradeNo("ORDER_004")
.amount(new BigDecimal("400"))
.build();
PaymentResult result = paymentContext.pay("digital_rmb", request);
assertTrue(result.isSuccess());
}
@Test
void testUnsupportedChannel() {
PaymentRequest request = PaymentRequest.builder()
.outTradeNo("ORDER_005")
.amount(new BigDecimal("100"))
.build();
assertThrows(BusinessException.class,
() -> paymentContext.pay("unknown", request));
}
@Test
void testRefund() {
PaymentResult result = paymentContext.refund(
"alipay", "ORDER_001", new BigDecimal("100"));
assertTrue(result.isSuccess());
}
}
五、策略 vs 工厂方法 / 状态模式
5.1 一张表看清区别
| 模式 | 关注点 | 行为 | 适用场景 |
|---|---|---|---|
| 策略 | 算法切换 | 互相独立 | 多算法 |
| 工厂方法 | 对象创建 | 返回对象 | 创建 |
| 状态 | 状态转换 | 状态机 | 状态变化 |
5.2 一个具体例子区分 3 个模式
大白商城:
| 模式 | 例子 |
|---|---|
| 策略 | 支付方式(4 种)------ 互相独立 |
| 工厂方法 | 创建支付客户端(按渠道) |
| 状态 | 订单状态机(待支付→已支付→已发货) |
5.3 决策树
有多种算法可切换?
├── 是
│ ├── 算法之间有状态转换?
│ │ ├── 是 → ✅ 状态模式
│ │ └── 否 → ✅ 策略模式
└── 否
├── 按类型创建对象?
│ ├── 是 → ✅ 工厂方法
│ └── 否 → 普通类
5.4 策略 vs 状态:易混淆
| 维度 | 策略 | 状态 |
|---|---|---|
| 算法关系 | 互相独立 | 互相转换 |
| 客户端 | 主动选择 | 状态机自动 |
| 数量 | 任意 | 有限 |
| 切换 | 运行时手动 | 状态机自动 |
大白商城选型:
| 场景 | 选哪个 | 原因 |
|---|---|---|
| 支付方式 | 策略 | 4 种独立 |
| 订单状态 | 状态 | 状态机 |
| 优惠规则 | 策略 | 互相独立 |
| 物流方式 | 策略 | 互相独立 |
六、Spring 的 ResourceLoader 怎么借鉴策略?
6.1 Spring ResourceLoader:资源加载策略
Spring 的资源加载用策略模式:
ResourceLoader(策略接口)
├── ClassPathResourceLoader
├── FileSystemResourceLoader
├── UrlResourceLoader
└── ...
6.2 核心源码(简化版)
java
/**
* ✅ Spring 策略:资源加载
*/
public interface ResourceLoader {
/**
* ✅ 策略方法
*/
Resource getResource(String location);
/**
* 类加载器
*/
ClassLoader getClassLoader();
}
6.3 大白商城的"抄作业"
java
/**
* 借鉴 Spring ResourceLoader:文件加载策略
*/
public interface FileLoaderStrategy {
byte[] load(String path);
}
@Component
public class LocalFileLoader implements FileLoaderStrategy {
@Override
public byte[] load(String path) {
// 从本地加载
return Files.readAllBytes(Paths.get(path));
}
}
@Component
public class OssFileLoader implements FileLoaderStrategy {
@Override
public byte[] load(String path) {
// 从 OSS 加载
return ossClient.getObject(path);
}
}
@Service
public class FileLoadContext {
private final Map<String, FileLoaderStrategy> loaderMap = new HashMap<>();
@Autowired
public FileLoadContext(List<FileLoaderStrategy> loaders) {
// 假设每个 loader 有 getType()
}
public byte[] load(String type, String path) {
return loaderMap.get(type).load(path);
}
}
七、策略 + 工厂方法怎么组合?
7.1 组合 1:策略 + 工厂方法 = 工厂创建策略
java
/**
* ✅ 策略 + 工厂:按 channel 字符串直接获取策略
*/
public class PaymentStrategyFactory {
private static final Map<String, PaymentStrategy> STRATEGY_MAP = new ConcurrentHashMap<>();
public static void register(PaymentStrategy strategy) {
STRATEGY_MAP.put(strategy.getChannel(), strategy);
}
public static PaymentStrategy getStrategy(String channel) {
return STRATEGY_MAP.get(channel);
}
}
7.2 组合 2:策略 + 单例 = 策略本身单例
java
/**
* ✅ 策略本身单例(Spring 默认单例)
*/
@Component
public class AlipayStrategy implements PaymentStrategy {
// Spring 单例
}
7.3 组合 3:策略 + 装饰器 = 策略叠加功能
java
/**
* ✅ 策略 + 装饰器:给策略加日志/权限
*/
public class PaymentStrategyProxy implements PaymentStrategy {
private final PaymentStrategy target;
public PaymentStrategyProxy(PaymentStrategy target) {
this.target = target;
}
@Override
public PaymentResult pay(PaymentRequest request) {
log.info("支付开始, channel={}", target.getChannel());
return target.pay(request);
}
}
八、工程决策 Checklist
8.1 ✅ 这 5 种情况,强烈建议用策略
| 序号 | 场景 | 原因 |
|---|---|---|
| ① | 多算法可切换 | 支付 / 优惠 / 物流 |
| ② | 加新算法不动老代码 | 符合开闭原则 |
| ③ | 算法独立测试 | 单元测试友好 |
| ④ | 运行时切换 | 灵活 |
| ⑤ | 业务方不关心算法细节 | 解耦 |
8.2 ❌ 这 5 种情况,绝对不要用策略
| 序号 | 场景 | 原因 |
|---|---|---|
| ① | 只有 1-2 个分支 | if-else 够用 |
| ② | 算法之间有状态 | 用状态模式 |
| ③ | 业务简单 | 过度设计 |
| ④ | 追求简单 | 策略增加类 |
| ⑤ | 算法频繁变 | 难维护 |
8.3 ⚠️ 策略的 6 大常见坑
| 序号 | 坑 | 表现 | 解决方案 |
|---|---|---|---|
| ① | 策略过多 | 100 个 Strategy 类 | 按业务拆分 |
| ② | 上下文臃肿 | Context 太大 | 保持 Context 简单 |
| ③ | 策略重复 | 多个类似策略 | 抽象公共逻辑 |
| ④ | 未注册 | 调不到策略 | Spring 自动注入 |
| ⑤ | 策略状态污染 | 策略持有可变状态 | 策略无状态 |
| ⑥ | 字符串 Key 错 | 调错策略 | 用枚举 |
8.4 面试官视角:策略高频追问
Q1:策略 vs 工厂方法,区别是什么?
答:策略关注"怎么做"(执行算法),工厂方法关注"造什么"(创建对象)。策略返回结果,工厂返回对象。
Q2:策略 vs 状态,区别是什么?
答:策略算法独立 (之间无转换),状态状态机(之间有转换)。支付方式之间无转换(策略),订单状态之间有转换(状态)。
Q3:Spring 的 ResourceLoader 是策略吗?
答:是的 。
ResourceLoader是策略接口,ClassPathResource/FileSystemResource等是具体策略。Spring 用DefaultResourceLoader作为默认策略。
九、与其他模式协作
9.1 策略 + 工厂方法 = 工厂创建策略
9.2 策略 + 单例 = 策略本身单例
9.3 策略 + 状态 = 状态机内用策略
java
/**
* 策略 + 状态:订单状态机内的策略
*/
public class OrderStateMachine {
private final Map<OrderStatus, OrderHandler> handlers = new HashMap<>();
public void handle(OrderStatus status) {
OrderHandler handler = handlers.get(status);
// 调策略
handler.process();
// 自动切到下一个状态
}
}
9.4 大白商城模式协作全景图
┌──────────────┐
│ 策略 │ ← 本篇
└──────┬───────┘
│
┌───────────┬───────┼───────┬───────────┐
│ │ │ │ │
┌───▼───┐ ┌────▼───┐ ┌▼────┐ ┌▼─────┐ ┌───▼────┐
│工厂方法│ │ 单例 │ │状态 │ │装饰器│ │ 模板 │
│(创建) │ │(单策略)│ │(机) │ │(增强)│ │ 方法 │
└───────┘ └────────┘ └──────┘ └──────┘ └───────┘
04 篇 02 篇 35 篇 16 篇 27 篇
十、本篇小结 + 下篇预告
10.1 本篇小结(5 个核心要点)
- 本质 :策略 = 可替换的算法,加新算法不动老代码。
- 场景 :支付/优惠/物流/排序,多算法可切换。
- 对比:策略 vs 工厂方法(执行 vs 创建)/ 策略 vs 状态(独立 vs 转换)。
- Spring ResourceLoader :经典策略实现,ClassPathResource / FileSystemResource。
- 避坑:策略过多 / 上下文臃肿 / 状态污染 / Key 错。
10.2 一句话总结
策略不是"换种方式写 if-else",是"用算法族 + 上下文分离业务和算法" 。大白商城从 800 行 if-else 变成 4 个 Strategy + 1 个 Context------加数字人民币只加 1 个类,老代码零改动。
10.3 知识脑图
策略模式
├── 3 大角色
│ ├── 抽象策略(PaymentStrategy)
│ ├── 具体策略(AlipayStrategy / WechatStrategy / ...)
│ └── 上下文(PaymentContext)
├── 4 个具体策略
│ ├── AlipayStrategy
│ ├── WechatStrategy
│ ├── UnionPayStrategy
│ └── DigitalRmbStrategy
├── 模式对比
│ ├── vs 工厂方法(执行 vs 创建)
│ ├── vs 状态(独立 vs 转换)
│ └── vs if-else(开闭 vs 简单)
├── Spring ResourceLoader
│ ├── ClassPathResource
│ ├── FileSystemResource
│ └── UrlResource
└── 模式协作
├── + 工厂方法(创建策略)
├── + 单例(策略本身)
└── + 状态(状态机内)
10.4 下篇预告
第 27 篇【策略模式 - 源码剖析篇】:JDK / Spring 中的策略实现
下一篇我们会深入源码,回答三个问题:
- JDK 的
Comparator怎么用策略实现排序? - Spring 的
InstantiationStrategy怎么用策略创建 Bean? - Spring 的
ResourceLoader怎么用策略加载资源?
并附完整的源码解读 + 流程图 + 大白商城的"抄作业"实践。
觉得对您有帮助,麻烦 点点关注啦 ,您的关注是我创作的最大动力~ 🎯