Java 23 种设计模式:从踩坑到精通 | 番外:策略模式 —— 电商支付方式切换实战

摘要:策略模式定义一系列算法,将每一个算法封装起来,并使它们可以相互替换。策略模式让算法的变化独立于使用算法的客户端,通过“组合 + 多态”彻底消除 if-else 条件判断。本文结合电商支付方式切换的场景,完整展示如何用策略模式动态切换支付宝、微信等支付渠道,并与状态模式深度对比,帮你掌握“算法对象化”的设计精髓。

🗺️ 本文阅读地图(3 分钟速览)

  • 为什么支付方式不能写死在订单类里?
  • 策略模式核心角色:抽象策略、具体策略、上下文
  • 手写支付策略:支付宝、微信动态切换
  • Lambda 简化:逻辑简单时不用写类
  • 面试必问:“策略模式和状态模式有什么区别?Comparator 用了哪个?”

📖 《Java 23 种设计模式:从踩坑到精通》
开篇:系列介绍与目录 | 正篇:Strategy 策略模式—— 算法族的封装与切换,告别 if-else | 当前:番外 · 策略模式 × 电商支付方式切换
🔗 返回系列总目录

1. 电商支付方式的痛点

在电商系统中,订单支付支持多种方式——支付宝、微信、银联等。如果直接在订单类中根据支付方式写 if-else

void pay(Order order, String type) {
    if ("alipay".equals(type)) {
        // 调用支付宝接口
    } else if ("wechat".equals(type)) {
        // 调用微信接口
    } else if ("unionpay".equals(type)) {
        // 调用银联接口
    }
    // 新增支付方式要改这里...
}

这种写法将订单与具体支付渠道紧耦合,新增一种支付方式(如抖音支付)就要修改订单类。更麻烦的是,如果用户在下单后想切换支付方式,静态 if-else 无法优雅支持。

策略模式的解决思路:将每种支付方式封装为独立的策略类,订单类(上下文)只持有抽象策略引用,通过 setStrategy() 在运行时动态切换。新增支付方式只需新增一个策略类,订单代码零修改。

1.1 你的场景该不该用策略模式?

判断标准 是 → 用策略模式 否 → 用其他方式
同一个操作有多种实现方式,且可能动态切换
算法逻辑复杂,相互独立
未来需要增加新算法,不希望修改原有代码
只有两三种固定不变的简单分支 直接用 if-else 即可

2. 策略模式 UML(电商支付方式场景)

在这里插入图片描述


3. 完整源码实现

3.1 抽象策略接口 (Strategy)

/**
 * 抽象策略:定义支付算法的公共接口
 */
public interface Strategy {
    void algorithm();
}

💬 白话:所有支付方式都必须实现 algorithm()——不管支付宝还是微信,最终都是“执行一个支付动作”。

3.2 具体策略A:支付宝支付 (ConcreteStrategyA)

/**
 * 具体策略A:支付宝支付
 */
public class ConcreteStrategyA implements Strategy {
    @Override
    public void algorithm() {
        System.out.println("💳 正在调用【支付宝】支付接口...");
        System.out.println("   🔗 跳转支付宝收银台...");
        System.out.println("   ✅ 支付宝支付成功!");
    }
}

3.3 具体策略B:微信支付 (ConcreteStrategyB)

/**
 * 具体策略B:微信支付
 */
public class ConcreteStrategyB implements Strategy {
    @Override
    public void algorithm() {
        System.out.println("💳 正在调用【微信支付】接口...");
        System.out.println("   📱 弹出微信付款码...");
        System.out.println("   ✅ 微信支付成功!");
    }
}

💬 白话:两个策略类实现完全相同的接口,但内部逻辑各自独立。新增银联支付只需新增一个 ConcreteStrategyC,支付宝和微信的代码完全不用动。

3.4 上下文:订单支付入口 (Context)

/**
 * 上下文:持有策略引用,对外提供统一调用入口
 */
public class Context {
    private Strategy strategy;

    /** 构造时注入策略(推荐方式,避免空指针) */
    public Context(Strategy strategy) {
        this.strategy = strategy;
    }

    /** 动态切换策略 */
    public void setStrategy(Strategy strategy) {
        this.strategy = strategy;
    }

    /** 执行当前策略的算法 */
    public void executeStrategy() {
        if (strategy == null) {
            throw new IllegalStateException("未设置支付策略!");
        }
        strategy.algorithm();
    }
}

💬 白话:上下文就是一个“支付入口”——它不关心用的是支付宝还是微信,只知道自己持有一个策略,执行时调用策略的 algorithm()。切换支付方式只需 setStrategy(),一行代码搞定。

3.5 客户端测试

public class Client {
    public static void main(String[] args) {
        // 场景1:用户选择支付宝支付
        System.out.println("=== 订单 #2024001 ===");
        Context context = new Context(new ConcreteStrategyA());
        context.executeStrategy();

        System.out.println();

        // 场景2:运行时动态切换为微信支付
        System.out.println("=== 订单 #2024002 ===");
        context.setStrategy(new ConcreteStrategyB());
        context.executeStrategy();
    }
}

4. 运行结果

=== 订单 #2024001 ===
💳 正在调用【支付宝】支付接口...
   🔗 跳转支付宝收银台...
   ✅ 支付宝支付成功!

=== 订单 #2024002 ===
💳 正在调用【微信支付】接口...
   📱 弹出微信付款码...
   ✅ 微信支付成功!

5. 核心角色回顾

角色 职责 对应代码
Strategy 定义所有支持算法的公共接口 Strategy
ConcreteStrategy 实现具体算法,每种策略独立 ConcreteStrategyA/B
Context 持有策略引用,提供统一调用入口 Context

6. Lambda 简化策略模式(Java 8+)

如果策略接口只有一个抽象方法(函数式接口),可以用 Lambda 表达式直接内联,避免为每种策略建一个类:

// 直接用 Lambda 替代具体策略类
Context alipay = new Context(() -> {
    System.out.println("💳 正在调用【支付宝】支付接口...");
});
alipay.executeStrategy();

// 微信支付
Context wechat = new Context(() -> {
    System.out.println("💳 正在调用【微信支付】接口...");
});
wechat.executeStrategy();

💬 白话:支付逻辑只有几行时,不需要专门写 ConcreteStrategyA / ConcreteStrategyB 类。Lambda 直接内联,代码更简洁。但如果策略逻辑复杂(如包含多步骤状态流转),独立类更清晰。

7. 策略模式 vs 状态模式

这是面试中极容易混淆的一对:

对比项 策略模式 状态模式
目的 封装算法族,使其可替换 行为随内部状态自动改变
谁触发切换 客户端显式调用 setStrategy() 状态对象自己调用 context.setState()
上下文感知 策略类通常不知道上下文的存在 状态类持有并操作上下文引用
典型应用 支付方式、排序算法、促销策略 订单状态机、JDK 线程状态

💡 一句话记忆:策略模式是“主动替换”——客户端选择用哪个算法;状态模式是“被动流转”——状态自己决定下一步去哪。本例中支付方式的切换由客户端调用 setStrategy() 触发,是典型的策略模式。

8. 策略模式的优缺点

优点 缺点
消除 if-else,用多态替代条件判断 策略类数量增多
运行时动态切换策略 客户端需了解各策略的特点才能正确选择
新增策略无需修改上下文,符合开闭原则 策略间通信成本较高
Lambda 可进一步简化简单策略 过度设计风险:2~3 种固定分支用 if-else 即可

9. 六大设计原则体现

原则 体现
单一职责 每种策略只负责一种算法实现
开闭原则 新增支付方式无需修改 Context
里氏替换 所有策略可替换 Strategy 接口
依赖倒置 Context 依赖抽象 Strategy 接口
接口隔离 Strategy 只有 algorithm() 一个方法
迪米特法则 Context 只知道策略接口,不知内部实现

附 策略模式 UML源码(电商支付方式场景)

@startuml
title Java 23 种设计模式:从踩坑到精通
footer 折哥 | 智能物流与Java实战

' 1. 全局样式配置
skinparam backgroundColor #FEFEFE
skinparam shadowing false
skinparam classBorderColor #333333
skinparam classFontColor #1A1A1A
skinparam classFontSize 14
skinparam noteFontSize 12
skinparam noteFontColor #555555
skinparam arrowColor #555555
skinparam classBackgroundColor #F9F9F9
skinparam interface {
    BackgroundColor #E8F5E9
    BorderColor #2E7D32
}

' 2. 抽象策略
interface Strategy {
    + algorithm()
}
note right of Strategy
  <b>抽象策略</b>
  --
  定义所有支持算法的公共接口
  Context 通过此接口调用具体算法
end note

' 3. 具体策略A
class ConcreteStrategyA implements Strategy {
    + algorithm()
}
note right of ConcreteStrategyA
  <b>具体策略A</b>
  --
  实现算法A
  例如:支付宝支付
end note

' 4. 具体策略B
class ConcreteStrategyB implements Strategy {
    + algorithm()
}
note right of ConcreteStrategyB
  <b>具体策略B</b>
  --
  实现算法B
  例如:微信支付
end note

' 5. 上下文
class Context {
    - strategy : Strategy
    + setStrategy(Strategy)
    + executeStrategy()
}
note right of Context
  <b>上下文</b>
  --
  持有一个 Strategy 引用
  可动态切换策略
  屏蔽具体策略的实现细节
end note

' 6. 关系连线
Context o-down-> Strategy : 持有
ConcreteStrategyA .up.> Strategy : 实现
ConcreteStrategyB .up.> Strategy : 实现

@enduml

🧭 《Java 23 种设计模式:从踩坑到精通》快速导航

🔔 关注《Java 23 种设计模式:从踩坑到精通》,用 25 篇文章彻底吃透设计模式。
📦 福利预告:全系列代码及 UML 源码将在完结时统一打包开放,点击「关注」「收藏」第一时间获取。

📌 除了设计模式,我也在深挖智能物流实战(WMS、托盘调度、机器学习落地)。欢迎点击头像,看看专栏 《出版社物流WMS智能调度实战》《电商多平台电子面单对接实战》。技术相通,思路可鉴。

Logo

电商企业物流数字化转型必备!快递鸟 API 接口,72 小时快速完成物流系统集成。全流程实战1V1指导,营造开放的API技术生态圈。

更多推荐