# 设计模式 —— 优化退款案例(策略模式 & 模板模式 & 工厂模式)
# 一、背景
在电商 App 的订单模块中,随着业务发展出现了多种订单类型(普通订单、团购订单、组合订单、服务卡订单等),不同订单的退款逻辑差异较大。同时,用户维度(如 VIP 用户、普通用户)也会影响退款流程,例如 VIP 用户可享受垫付退款,普通用户则需等待 2 个工作日。
原有代码因多次迭代新增逻辑,导致大量 if-else 嵌套,代码混乱且难以维护。本次重构通过策略模式、模板模式和工厂模式整合用户与订单维度的复杂逻辑,实现代码解耦与扩展。
# 二、设计方案
# 1. 策略模式(用户维度)
适用场景:
- 不同用户(VIP / 普通用户)的退款逻辑不同,需动态选择行为。
- 避免多重条件判断(如
if-else ),将算法封装为独立策略类。
核心思想:定义策略接口,具体策略实现差异化逻辑,通过工厂模式管理策略实例。
# 2. 模板模式(订单维度)
适用场景:
- 不同订单(普通 / 团购订单)的退款流程包含公共步骤(订单校验、更新退款表、计算空跑费),但部分步骤需子类实现。
- 提取公共逻辑到抽象模板类,子类覆盖差异化步骤。
# 3. 工厂模式
作用:作为容器管理策略模式的实例,通过枚举键值对映射策略类,解耦对象创建逻辑。
# 三、代码实战
# 1. 基础枚举定义
# 用户等级枚举(UserLevelEnum)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public enum UserLevelEnum { NORMAL(1, "普通用户"), VIP(2, "VIP用户");
private Integer code; private String desc;
UserLevelEnum(Integer code, String desc) { this.code = code; this.desc = desc; }
public Integer getCode() { return code; } }
|
# 订单类型枚举(OrderTypeEnum)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| public enum OrderTypeEnum { NORMAL_ORDER(1, "普通订单"), GROUP_ORDER(2, "团购订单"), COMBO_ORDER(3, "组合订单");
private Integer code; private String desc;
OrderTypeEnum(Integer code, String desc) { this.code = code; this.desc = desc; }
public Integer getCode() { return code; } }
|
# 2. 策略模式实现(用户退款策略)
# 策略接口(IRefundUserStrategy)
1 2 3
| public interface IRefundUserStrategy { void doRefundByUser(OrderInfo refundVO); }
|
# 普通用户策略(NormalUserRefundStrategy)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @Component public class NormalUserRefundStrategy implements IRefundUserStrategy {
@Autowired private OrderRefundComponent orderRefundComponent;
@PostConstruct private void initStrategy() { RefundUserStrategyFactory.putStrategy(UserLevelEnum.NORMAL, this); }
@Override public void doRefundByUser(OrderInfo refundVO) { log.info("普通用户退款流程:需2个工作日到账"); AbstractOrderRefundTemplate template = orderRefundComponent.getOrderTemplate(refundVO.getOrderType()); template.executeRefund(); } }
|
# VIP 用户策略(VipUserRefundStrategy)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @Component public class VipUserRefundStrategy implements IRefundUserStrategy {
@Autowired private OrderRefundComponent orderRefundComponent;
@PostConstruct private void initStrategy() { RefundUserStrategyFactory.putStrategy(UserLevelEnum.VIP, this); }
@Override public void doRefundByUser(OrderInfo refundVO) { log.info("VIP用户退款流程:第三方垫付立即到账"); AbstractOrderRefundTemplate template = orderRefundComponent.getOrderTemplate(refundVO.getOrderType()); template.executeRefund(); } }
|
# 策略工厂(RefundUserStrategyFactory)
1 2 3 4 5 6 7 8 9 10 11 12 13
| public class RefundUserStrategyFactory { private static final Map<UserLevelEnum, IRefundUserStrategy> strategyMap = new HashMap<>();
public static IRefundUserStrategy getStrategy(UserLevelEnum userLevel) { return strategyMap.get(userLevel); }
public static void putStrategy(UserLevelEnum userLevel, IRefundUserStrategy strategy) { strategyMap.put(userLevel, strategy); } }
|
# 3. 模板模式实现(订单退款模板)
# 抽象模板类(AbstractOrderRefundTemplate)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public abstract class AbstractOrderRefundTemplate {
public final void executeRefund() { checkOrder(); updateRefundRecord(); calculateEmptyRunFee(); doFinalRefund(); }
protected void checkOrder() { log.info("订单校验通过"); }
protected void updateRefundRecord() { log.info("退款记录更新至数据库"); }
protected abstract void calculateEmptyRunFee(); protected abstract void doFinalRefund(); }
|
# 普通订单模板(NormalOrderRefundTemplate)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| @Component public class NormalOrderRefundTemplate extends AbstractOrderRefundTemplate {
@Override protected void calculateEmptyRunFee() { OrderInfo order = OrderContext.getOrder(); double fee = order.getPaymentType() == 1 ? Math.min(order.getAmount() * 0.1, 5) : 10; log.info("普通订单空跑费计算:{}元", fee); }
@Override protected void doFinalRefund() { log.info("普通订单退款完成,金额已退回用户账户"); } }
|
# 团购订单模板(GroupOrderRefundTemplate)
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Component public class GroupOrderRefundTemplate extends AbstractOrderRefundTemplate {
@Override protected void calculateEmptyRunFee() { log.info("团购订单空跑费:0元"); }
@Override protected void doFinalRefund() { log.info("团购订单退款完成,金额原路返回"); } }
|
# 订单模板管理组件(OrderRefundComponent)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| @Component public class OrderRefundComponent {
private static final Map<OrderTypeEnum, AbstractOrderRefundTemplate> templateMap = new HashMap<>();
@Autowired public void setTemplateMap(List<AbstractOrderRefundTemplate> templates) { templates.forEach(template -> templateMap.put(OrderTypeEnum.valueOf(template.getClass().getSimpleName().split("Refund")[0]), template) ); }
public AbstractOrderRefundTemplate getOrderTemplate(OrderTypeEnum orderType) { return templateMap.get(orderType); } }
|
# 4. 退款入口逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| @Service public class RefundService {
public void processRefund(OrderInfo refundVO) throws Exception { try { checkBasicInfo(refundVO); UserLevelEnum userLevel = UserLevelEnum.valueOf(refundVO.getUserId()); IRefundUserStrategy strategy = RefundUserStrategyFactory.getStrategy(userLevel); strategy.doRefundByUser(refundVO); } catch (Exception e) { log.error("退款异常:{}", e.getMessage(), e); throw new RefundException("退款失败"); } }
private void checkBasicInfo(OrderInfo refundVO) { if (refundVO.getUserId() == null || refundVO.getOrderId() == null) { throw new IllegalArgumentException("必要参数缺失"); } } }
|
# 四、模式协作关系
- 策略模式:通过
UserLevelEnum 区分用户类型,不同策略类( NormalUserRefundStrategy / VipUserRefundStrategy )处理差异化的用户退款流程。 - 模板模式:
AbstractOrderRefundTemplate 定义公共流程(订单校验、更新记录),具体订单模板(普通 / 团购订单)实现差异化的空跑费计算和最终退款逻辑。 - 工厂模式:
RefundUserStrategyFactory 和 OrderRefundComponent 作为容器,通过枚举映射策略和模板实例,避免硬编码 if-else 。
# 五、优化效果
- 代码结构清晰:移除大量
if-else ,逻辑按维度(用户 / 订单)分离,可读性提升。 - 易于扩展:新增订单类型或用户策略时,只需新增对应策略类 / 模板类,符合开闭原则。
- 复用性强:公共逻辑(订单校验、数据库操作)在模板类中统一实现,减少代码冗余。
# 六、总结
当系统中存在多维度差异化逻辑(如用户等级、订单类型)且包含公共流程时,可组合使用策略模式(处理横向差异化)、模板模式(处理纵向公共流程)和工厂模式(管理对象实例)。这种组合模式能有效解决 if-else 爆炸问题,提升代码可维护性和扩展性。
实践建议:
- 优先识别业务中的 “变” 与 “不变” 部分,“不变” 逻辑抽象为模板,“变” 的部分封装为策略。
- 结合 Spring 框架的依赖注入和初始化注解(
@PostConstruct ),简化工厂模式的实例注册流程。 - 复杂场景可引入枚举类统一管理类型标识,避免魔法值污染代码。