大白话说Java设计模式-26-策略模式(业务实战篇)

📌 PDF :AI人工智能 --- AI Agent智能体实战项目

大白话说Java设计模式-26-策略模式(业务实战篇):大白商城支付策略的"算法切换"

📌 一句话本质:策略模式就是"可替换的算法",让算法独立于使用它的客户。

🏷️ 标签:策略模式 / Java 设计模式 / 算法切换 / 支付策略 / 大白商城 🎯 适合:初中级后端 / 经常被"多种算法"困扰的工程师


目录


一、业务场景引入:为什么需要"可切换的算法"?

大白商城 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 里
  • 正确做法
    1. 把每种支付封装成独立策略AlipayStrategy / WechatStrategy / UnionPayStrategy / DigitalRmbStrategy
    2. 业务方只调"支付上下文":PaymentContext
    3. 上下文根据渠道自动选择对应策略
    4. 加新支付?新建一个 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 个核心要点)

  1. 本质 :策略 = 可替换的算法,加新算法不动老代码
  2. 场景 :支付/优惠/物流/排序,多算法可切换
  3. 对比:策略 vs 工厂方法(执行 vs 创建)/ 策略 vs 状态(独立 vs 转换)。
  4. Spring ResourceLoader :经典策略实现,ClassPathResource / FileSystemResource
  5. 避坑:策略过多 / 上下文臃肿 / 状态污染 / 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 中的策略实现

下一篇我们会深入源码,回答三个问题:

  1. JDK 的 Comparator 怎么用策略实现排序?
  2. Spring 的 InstantiationStrategy 怎么用策略创建 Bean?
  3. Spring 的 ResourceLoader 怎么用策略加载资源?

并附完整的源码解读 + 流程图 + 大白商城的"抄作业"实践。


觉得对您有帮助,麻烦 点点关注啦 ,您的关注是我创作的最大动力~ 🎯

相关推荐
不是光头 强1 小时前
Java 后端 AI 技术选型与学习路线
java·人工智能·学习
paopaokaka_luck2 小时前
基于springboot3+vue3的文山民族文化资源展示与推广平台(协同过滤算法、Echarts 图形化分析)
java·前端·spring boot·学习·echarts
eralong2 小时前
Java IO 与 NIO
java·后端
Jesse_EC2 小时前
我给消息推送加了 Publisher Confirm,然后亲手引入了一个竞态
java
S3rein2 小时前
JAVA多线程手撕
java·多线程
用户8181870627462 小时前
第7章 死锁排查实录:jstack定位死锁的完整流程
java·后端
刀锋00012 小时前
Java 工程师转 AI Agent 最短路径:用 Spring 的思维理解 Spring AI 2.0(附 LangChain4j 对照)
java·人工智能·spring boot·spring·ai agent
喵同志不止步于码农3 小时前
Java Caffeine 快速入门
java·开发语言·后端·并发·缓存一致性
Java成神之路-3 小时前
Java 并发灵魂拷问:notify/signal 唤醒顺序是规范还是实现?
java