Java 设计模式实战:模板方法 + 工厂 + 策略模式重构支付系统

设计模式三剑客:模板方法+工厂+策略,一套组合拳解决支付系统难题

别再写 if-else 了! 当你的代码里出现第 10 个 if (type.equals("wechat")) 的时候,就该停下来想想:是不是该用设计模式了?


一、问题背景:当支付方式从 3 种变成 10 种

上周在重构公司的电商支付模块时,我看到了一段让我头皮发麻的代码:

java 复制代码
public class PaymentService {
    public void pay(String payType, Order order) {
        if ("alipay".equals(payType)) {
            System.out.println("支付宝支付逻辑...");
        } else if ("wechat".equals(payType)) {
            System.out.println("微信支付逻辑...");
        } else if ("unionpay".equals(payType)) {
            System.out.println("银联支付逻辑...");
        } else if ("jd_pay".equals(payType)) {
            System.out.println("京东支付逻辑...");
        }
    }
}

问题来了:

  • ❌ 每次新增支付方式,都要改这个方法(违反开闭原则)
  • ❌ 支付流程中的校验、计算、日志等公共逻辑散落各处
  • ❌ 新人接手时,看一眼就想跑路 😅

这代码能跑,但已经埋雷了。

今天我就用模板方法 + 工厂方法 + 策略模式这套组合拳,把这个"屎山"变成优雅的架构。


二、先搞懂这三个模式到底是啥

2.1 用生活化例子理解三个模式

设计模式 生活类比 核心作用
模板方法 做菜的固定步骤(洗→切→炒→装) 定义流程骨架
工厂方法 根据菜单找厨师(川菜厨/粤菜厨) 创建对象
策略模式 不同菜系的炒法(爆炒/清蒸/红烧) 封装算法

2.2 三者如何协作?

复制代码
用户请求:"我要用支付宝付100块"
         ↓
【模板方法】定义标准流程:
  ① 校验订单 → ② 计算金额 → ③ 调用支付 → ④ 记录日志
         ↓
【工厂方法】根据 "alipay" 找到对应的支付策略
         ↓
【策略模式】执行支付宝的具体支付算法
         ↓
返回结果:"支付成功"

一句话总结:

模板方法管流程,工厂方法管创建,策略模式管实现。


三、实战案例:电商支付系统重构

3.1 场景设定

假设我们要做一个支持多种支付方式的电商系统:

支付方式 特殊逻辑
微信支付 需要调用微信SDK,有红包优惠
支付宝 需要调用支付宝SDK,有花呗分期
银联支付 需要调用银联接口,需要验证银行卡

共同流程:

  1. 参数校验
  2. 金额计算(含优惠)
  3. 调用第三方支付
  4. 记录支付日志

四、完整代码实现(Spring Boot 版)

4.1 项目结构

复制代码
src/main/java/com/example/payment/
├── entity/
│   └── Order.java                    # 订单实体
├── strategy/
│   ├── PaymentStrategy.java          # 策略接口
│   ├── AlipayStrategy.java           # 支付宝策略
│   ├── WechatPayStrategy.java        # 微信支付策略
│   └── UnionPayStrategy.java         # 银联支付策略
├── factory/
│   └── PaymentStrategyFactory.java   # 策略工厂
└── service/
    └── AbstractPaymentService.java   # 模板方法抽象类

4.2 定义订单实体

java 复制代码
package com.example.payment.entity;

import lombok.Data;
import java.math.BigDecimal;

@Data
public class Order {
    private String orderId;
    private BigDecimal amount;
    private String payType;
    private String userId;
    
    public Order(String orderId, BigDecimal amount, String payType, String userId) {
        this.orderId = orderId;
        this.amount = amount;
        this.payType = payType;
        this.userId = userId;
    }
}

4.3 定义策略接口(策略模式核心)

java 复制代码
package com.example.payment.strategy;

import com.example.payment.entity.Order;
import java.math.BigDecimal;

public interface PaymentStrategy {
    
    String getPayType();
    
    BigDecimal calculateDiscount(Order order);
    
    boolean doPayment(Order order, BigDecimal finalAmount);
}

关键点:

  • getPayType():标识策略类型(给工厂用的)
  • calculateDiscount():计算优惠金额(各平台不同)
  • doPayment():真正调用第三方 SDK

4.4 实现具体策略

支付宝策略
java 复制代码
package com.example.payment.strategy;

import com.example.payment.entity.Order;
import lombok.extern.slf4j.Slf4j;
import java.math.BigDecimal;

@Slf4j
public class AlipayStrategy implements PaymentStrategy {

    @Override
    public String getPayType() {
        return "alipay";
    }

    @Override
    public BigDecimal calculateDiscount(Order order) {
        log.info("支付宝:计算花呗分期优惠...");
        return order.getAmount().multiply(new BigDecimal("0.95"));
    }

    @Override
    public boolean doPayment(Order order, BigDecimal finalAmount) {
        log.info("调用支付宝SDK,支付金额:{}", finalAmount);
        return true;
    }
}
微信支付策略
java 复制代码
package com.example.payment.strategy;

import com.example.payment.entity.Order;
import lombok.extern.slf4j.Slf4j;
import java.math.BigDecimal;

@Slf4j
public class WechatPayStrategy implements PaymentStrategy {

    @Override
    public String getPayType() {
        return "wechat";
    }

    @Override
    public BigDecimal calculateDiscount(Order order) {
        log.info("微信支付:计算随机红包优惠...");
        double randomDiscount = 0.9 + Math.random() * 0.05;
        return order.getAmount().multiply(new BigDecimal(randomDiscount));
    }

    @Override
    public boolean doPayment(Order order, BigDecimal finalAmount) {
        log.info("调用微信支付SDK,支付金额:{}", finalAmount);
        return true;
    }
}
银联支付策略
java 复制代码
package com.example.payment.strategy;

import com.example.payment.entity.Order;
import lombok.extern.slf4j.Slf4j;
import java.math.BigDecimal;

@Slf4j
public class UnionPayStrategy implements PaymentStrategy {

    @Override
    public String getPayType() {
        return "unionpay";
    }

    @Override
    public BigDecimal calculateDiscount(Order order) {
        log.info("银联支付:无优惠(老实人)");
        return order.getAmount();
    }

    @Override
    public boolean doPayment(Order order, BigDecimal finalAmount) {
        log.info("调用银联接口,验证银行卡后支付:{}", finalAmount);
        return true;
    }
}

4.5 创建策略工厂(工厂方法核心)

java 复制代码
package com.example.payment.factory;

import com.example.payment.strategy.PaymentStrategy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Component
public class PaymentStrategyFactory {

    @Autowired
    private List<PaymentStrategy> strategies;

    private Map<String, PaymentStrategy> strategyMap = new HashMap<>();

    @PostConstruct
    public void init() {
        for (PaymentStrategy strategy : strategies) {
            strategyMap.put(strategy.getPayType(), strategy);
        }
    }

    public PaymentStrategy getStrategy(String payType) {
        PaymentStrategy strategy = strategyMap.get(payType);
        if (strategy == null) {
            throw new IllegalArgumentException("不支持的支付类型:" + payType);
        }
        return strategy;
    }
}

亮点解析:

自动注册 :利用 Spring 的依赖注入,新策略只需加 @Component

Map 缓存 :避免每次都遍历列表,O(1) 查找

防御性编程:不支持的类型直接抛异常

4.6 定义模板方法(模板方法模式核心)

java 复制代码
package com.example.payment.service;

import com.example.payment.entity.Order;
import com.example.payment.factory.PaymentStrategyFactory;
import com.example.payment.strategy.PaymentStrategy;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;

@Slf4j
@Service
public abstract class AbstractPaymentService {

    @Autowired
    protected PaymentStrategyFactory strategyFactory;

    public final PaymentResult executePayment(Order order) {
        
        try {
            Step1: validateOrder(order);
            
            Step2: PaymentStrategy strategy = strategyFactory.getStrategy(order.getPayType());
            
            Step3: BigDecimal finalAmount = strategy.calculateDiscount(order);
            
            Step4: boolean success = strategy.doPayment(order, finalAmount);
            
            Step5: recordLog(order, success, finalAmount);
            
            return new PaymentResult(success, finalAmount, "支付完成");
            
        } catch (Exception e) {
            log.error("支付异常", e);
            return new PaymentResult(false, order.getAmount(), e.getMessage());
        }
    }

    protected void validateOrder(Order order) {
        log.info("【模板步骤1】校验订单:{}", order.getOrderId());
        if (order == null || order.getAmount() == null) {
            throw new IllegalArgumentException("订单参数不合法");
        }
    }

    protected void recordLog(Order order, boolean success, BigDecimal amount) {
        log.info("【模板步骤5】记录日志 - 订单:{} 结果:{} 金额:{}", 
                 order.getOrderId(), success ? "成功" : "失败", amount);
    }
}

为什么用 final

防止子类重写整个流程! 模板方法的核心就是固定骨架,子类只能扩展细节,不能改变流程。

4.7 具体业务服务类

java 复制代码
package com.example.payment.service;

import com.example.payment.entity.Order;
import org.springframework.stereotype.Service;

@Service
public class PaymentServiceImpl extends AbstractPaymentService {

}

看懂了吗?

子类啥都不用写!因为所有逻辑都在模板里了。

如果未来某个支付方式需要特殊处理(比如京东支付需要白条校验),可以重写特定步骤:

java 复制代码
@Service
public class JdPaymentServiceImpl extends AbstractPaymentService {

    @Override
    protected void validateOrder(Order order) {
        super.validateOrder(order);
        log.info("额外校验:检查用户是否有白条额度");
    }
}

4.8 返回结果实体

java 复制代码
package com.example.payment.service;

import lombok.AllArgsConstructor;
import lombok.Data;
import java.math.BigDecimal;

@Data
@AllArgsConstructor
public class PaymentResult {
    private boolean success;
    private BigDecimal amount;
    private String message;
}

4.9 测试 Controller

java 复制代码
package com.example.payment.controller;

import com.example.payment.entity.Order;
import com.example.payment.service.AbstractPaymentService;
import com.example.payment.service.PaymentResult;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.math.BigDecimal;

@RestController
@RequestMapping("/api/payment")
public class PaymentController {

    @Autowired
    private AbstractPaymentService paymentService;

    @PostMapping("/pay")
    public PaymentResult pay(@RequestBody Order order) {
        return paymentService.executePayment(order);
    }
}

4.10 测试请求示例

json 复制代码
POST /api/payment/pay
Content-Type: application/json

{
    "orderId": "ORD20260526001",
    "amount": 100.00,
    "payType": "alipay",
    "userId": "U001"
}

返回结果:

json 复制代码
{
    "success": true,
    "amount": 95.00,
    "message": "支付完成"
}

五、运行效果演示

当我们发起不同支付类型的请求时,系统会:

5.1 支付宝支付日志

text 复制代码
INFO  - 【模板步骤1】校验订单:ORD20260526001
INFO  - 支付宝:计算花呗分期优惠...
INFO  - 调用支付宝SDK,支付金额:95.00
INFO  - 【模板步骤5】记录日志 - 订单:ORD20260526001 结果:成功 金额:95.00

5.2 微信支付日志

text 复制代码
INFO  - 【模板步骤1】校验订单:ORD20260526001
INFO  - 微信支付:计算随机红包优惠...
INFO  - 调用微信支付SDK,支付金额:93.50
INFO  - 【模板步骤5】记录日志 - 订单:ORD20260526001 结果:成功 金额:93.50

5.3 银联支付日志

text 复制代码
INFO  - 【模板步骤1】校验订单:ORD20260526001
INFO  - 银联支付:无优惠(老实人)
INFO  - 调用银联接口,验证银行卡后支付:100.00
INFO  - 【模板步骤5】记录日志 - 订单:ORD20260526001 结果:成功 金额:100.00

同样的入口,不同的执行路径,这就是设计模式的魅力!


六、如何扩展新的支付方式?(重点!)

假设产品经理跑来说:"我们要接入抖音支付!"

传统方式的痛苦:

java 复制代码
else if ("douyin".equals(payType)) {
    System.out.println("抖音支付逻辑..."); // 改原代码,容易出bug
}

设计模式的优雅:

Step 1:新建一个策略类

java 复制代码
package com.example.payment.strategy;

import com.example.payment.entity.Order;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;

@Slf4j
@Component
public class DouyinPayStrategy implements PaymentStrategy {

    @Override
    public String getPayType() {
        return "douyin";
    }

    @Override
    public BigDecimal calculateDiscount(Order order) {
        log.info("抖音支付:新人立减5元");
        return order.getAmount().subtract(new BigDecimal("5"));
    }

    @Override
    public boolean doPayment(Order order, BigDecimal finalAmount) {
        log.info("调用抖音支付SDK,支付金额:{}", finalAmount);
        return true;
    }
}

Step 2:测试

json 复制代码
{
    "payType": "douyin",
    "amount": 100.00
}

完事了!

没有修改任何原有代码,只是新增了一个类。 这就是开闭原则的完美体现!


七、避坑指南(踩过坑的经验总结)

🚨 我踩过的坑

坑 1:工厂初始化时机问题
java 复制代码
// ❌ 错误:在构造函数中使用 @Autowired 的字段
public PaymentStrategyFactory() {
    for (PaymentStrategy s : strategies) { // NPE!此时还是null
        strategyMap.put(s.getPayType(), s);
    }
}

// ✅ 正确:使用 @PostConstruct
@PostConstruct
public void init() {
    for (PaymentStrategy s : strategies) {
        strategyMap.put(s.getPayType(), s);
    }
}

原因: Spring 注入字段是在构造函数之后才完成的。

坑 2:忘记加 @Component
java 复制代码
// ❌ 写了策略类但没加注解,工厂找不到
public class AlipayStrategy implements PaymentStrategy { ... }

// ✅ 必须加上
@Component
public class AlipayStrategy implements PaymentStrategy { ... }

症状: 调用时抛出 IllegalArgumentException: 不支持的支付类型,但你明明写了啊!

坑 3:模板方法没用 final
java 复制代码
// ❌ 子类可能重写整个流程
public PaymentResult executePayment(Order order) { ... }

// ✅ 锁死流程,只允许扩展步骤
public final PaymentResult executePayment(Order order) { ... }

后果: 某个新手开发者重写了你的模板方法,把校验逻辑给删了...

⚠️ 容易忽略的问题

问题 解决方案
策略类越来越多怎么办? 按模块分包:strategy/alipay/, strategy/wechat/
工厂类变得臃肿? 结合工厂模式+配置文件,用 Map 自动注入
需要动态添加策略? 使用数据库配置+反射机制(高级玩法)
策略之间有依赖关系? 考虑责任链模式替代

🔥 线上环境注意事项

  1. 支付超时处理 :在 doPayment() 里加重试机制
  2. 幂等性保证:同一订单不能重复扣款
  3. 降级方案:某支付渠道挂了,自动切换备用渠道
  4. 监控告警:每种支付方式的成功率、耗时都要监控

💡 性能建议

java 复制代码
// 如果策略很多,考虑用枚举代替字符串
public enum PayType {
    ALIPAY("alipay"),
    WECHAT("wechat"),
    UNIONPAY("unionpay");
    
    private final String code;
}

好处: 编译期检查,避免拼写错误。

🔒 安全建议

  • 支付金额必须使用 BigDecimal,禁止用 double(精度丢失问题)
  • 第三方 SDK 调用要做签名验证
  • 敏感信息(密钥、证书)不要硬编码,放配置中心

八、三种模式的对比总结

维度 单独使用 组合使用(本文方案)
可维护性 一般 ⭐⭐⭐⭐⭐
扩展性 较差 ⭐⭐⭐⭐⭐
代码复用 ⭐⭐⭐⭐⭐
学习成本 中等
适用场景 简单业务 复杂业务流程

什么时候用这套组合拳?

✅ 业务流程固定,但具体实现多变

✅ 需要频繁新增算法/策略

✅ 团队协作,需要降低耦合度

✅ 代码 review 时看到满屏 if-else 想吐 😂

什么时候不用?

❌ 只有 2-3 个固定分支,且很少变化

❌ 团队成员对设计模式不熟悉

❌ 项目赶时间,先求能用再求优雅


九、总结

今天我们用模板方法 + 工厂方法 + 策略模式这三种设计模式,把一个充满 if-else 的支付系统重构成了优雅的架构。

核心要点回顾:

  1. 模板方法 :定义固定的业务流程骨架(final 方法)
  2. 工厂方法 :根据类型动态创建策略对象(Map 缓存)
  3. 策略模式:封装不同支付方式的具体算法(接口+实现)

代码量对比:

方式 新增支付方式所需改动
if-else 修改原方法 + 加分支
设计模式 只新增一个类

这就是设计模式的价值:不是减少代码量,而是让代码更易维护、更易扩展。

🙏 作者介绍

📌 写文不易,Bug 更不易。

如果这篇文章对你有帮助,可以搜一搜:空门技术栈

这里分享:

  • ✅ Java / Spring AI / 企业级项目实战
  • ✅ Docker / RAG知识库 / 微服务踩坑
  • ✅ Python、前端、AI应用落地
  • ✅ 偶尔分享一些「头发保卫战」经验 😆

一个热爱技术、持续填坑的开发者,

陪你一起少踩坑,少加班,多写优雅代码。

📖 推荐阅读


🤝 技术交流 / 项目合作

平时也会做一些技术项目与咨询,包括:

  • Java / Spring Boot 企业级项目开发
  • AI 应用开发(LangChain、RAG、Agent、知识库)
  • Docker / Linux / 私有化部署
  • 系统功能开发、接口对接、性能优化
  • 疑难问题排查与技术咨询

如果你:

  • 想做 AI 项目,但不确定技术方案
  • 项目卡在某个 Bug 很久
  • 想把 AI 接入现有系统
  • 需要企业级开发支持

欢迎交流。

📮 联系方式:

  • Email:2929119150@qq.com
  • 也可以私信我
  • 技术交流可通过个人主页联系

有些坑,一个人踩是事故;一起踩,就是经验 😎


作者:IT空门 · 门主
标签:分享 AI 与代码,顺便抢救发际线
专注:能跑、能用、能落地

相关推荐
Larcher14 小时前
AI Loop:让AI像人一样自主完成任务的核心机制
javascript·人工智能·设计模式
用户35218024547514 小时前
当 Prompt 学会"热更新":Spring Boot × Nacos3 AI 实战
java·spring boot·ai编程
东坡白菜17 小时前
破局全栈:一个前端开发的Java入门实战记录(1)
java·全栈
唐青枫17 小时前
Java Tomcat 实战指南:从 Servlet 容器到 Spring Boot 部署
java
wsaaaqqq18 小时前
roudan:自由选择实体、灵活操作数据、快速写入数据库的 Java 框架
java
plainGeekDev21 小时前
null 判断 → Kotlin 可空类型
android·java·kotlin
糖拌西瓜皮21 小时前
Java开发者视角:深入理解Node.js异步编程模型
java·后端·node.js
plainGeekDev21 小时前
getter/setter → Kotlin 属性
android·java·kotlin
一线大码1 天前
Smart-Doc 的简单使用
java·后端·restful
MacroZheng1 天前
Claude Code官方桌面端正式发布,夯爆了!
java·人工智能·后端