设计模式三剑客:模板方法+工厂+策略,一套组合拳解决支付系统难题
别再写 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,有花呗分期 |
| 银联支付 | 需要调用银联接口,需要验证银行卡 |
共同流程:
- 参数校验
- 金额计算(含优惠)
- 调用第三方支付
- 记录支付日志
四、完整代码实现(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 自动注入 |
| 需要动态添加策略? | 使用数据库配置+反射机制(高级玩法) |
| 策略之间有依赖关系? | 考虑责任链模式替代 |
🔥 线上环境注意事项
- 支付超时处理 :在
doPayment()里加重试机制 - 幂等性保证:同一订单不能重复扣款
- 降级方案:某支付渠道挂了,自动切换备用渠道
- 监控告警:每种支付方式的成功率、耗时都要监控
💡 性能建议
java
// 如果策略很多,考虑用枚举代替字符串
public enum PayType {
ALIPAY("alipay"),
WECHAT("wechat"),
UNIONPAY("unionpay");
private final String code;
}
好处: 编译期检查,避免拼写错误。
🔒 安全建议
- 支付金额必须使用
BigDecimal,禁止用double(精度丢失问题) - 第三方 SDK 调用要做签名验证
- 敏感信息(密钥、证书)不要硬编码,放配置中心
八、三种模式的对比总结
| 维度 | 单独使用 | 组合使用(本文方案) |
|---|---|---|
| 可维护性 | 一般 | ⭐⭐⭐⭐⭐ |
| 扩展性 | 较差 | ⭐⭐⭐⭐⭐ |
| 代码复用 | 低 | ⭐⭐⭐⭐⭐ |
| 学习成本 | 低 | 中等 |
| 适用场景 | 简单业务 | 复杂业务流程 |
什么时候用这套组合拳?
✅ 业务流程固定,但具体实现多变
✅ 需要频繁新增算法/策略
✅ 团队协作,需要降低耦合度
✅ 代码 review 时看到满屏 if-else 想吐 😂
什么时候不用?
❌ 只有 2-3 个固定分支,且很少变化
❌ 团队成员对设计模式不熟悉
❌ 项目赶时间,先求能用再求优雅
九、总结
今天我们用模板方法 + 工厂方法 + 策略模式这三种设计模式,把一个充满 if-else 的支付系统重构成了优雅的架构。
核心要点回顾:
- 模板方法 :定义固定的业务流程骨架(
final方法) - 工厂方法 :根据类型动态创建策略对象(
Map缓存) - 策略模式:封装不同支付方式的具体算法(接口+实现)
代码量对比:
| 方式 | 新增支付方式所需改动 |
|---|---|
| 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 与代码,顺便抢救发际线
专注:能跑、能用、能落地