我项目里的策略模式:从if-else地狱到可插拔业务逻辑

我项目里的策略模式:从if-else地狱到可插拔业务逻辑

订单售后处理是个典型的多条件业务场景:不同售后类型(退款、退货退款、换货)有不同的处理流程,不同商品类型(实物、虚拟商品、服务)有不同的规则,不同渠道(APP、小程序、第三方平台)有不同的限制。如果用 if-else 写,代码会变成这样:

public void processAfterSale(AfterSaleRequest request) {
    AfterSaleOrder order = request.getOrder();

    // 条件1:售后类型
    if ("REFUND".equals(order.getType())) {
        // 退款逻辑
        if ("PHYSICAL".equals(order.getProductType())) {
            // 实物退款
            refundPhysicalProduct(order);
        } else if ("VIRTUAL".equals(order.getProductType())) {
            // 虚拟商品退款
            refundVirtualProduct(order);
        }
        // 条件2:渠道
        if ("APP".equals(order.getChannel())) {
            // APP额外处理
            handleAppRefund(order);
        }
    } else if ("RETURN_REFUND".equals(order.getType())) {
        // 退货退款逻辑
        if (order.getAmount().compareTo(new BigDecimal("1000")) > 0) {
            // 大额需要审批
            submitForApproval(order);
        }
        // ... 更多条件
    }

    // 20+ 个 else if,1000+ 行代码
}

这种代码的维护成本极高,每次加新规则都要在正确的位置插 if-else,还要小心翼翼不破坏现有逻辑。

策略模式的雏形

策略模式的核心是:把每种算法/规则封装成独立的对象,使用方不需要知道具体是哪种策略。

第一步:定义策略接口

public interface AfterSaleStrategy {
    /**
     * 判断当前策略是否适用于这个售后单
     */
    boolean supports(AfterSaleOrder order);

    /**
     * 执行售后处理
     */
    void process(AfterSaleOrder order);

    /**
     * 策略优先级,数字越小优先级越高
     */
    default int getOrder() {
        return 0;
    }
}

第二步:实现具体策略

@Component
public class RefundPhysicalProductStrategy implements AfterSaleStrategy {

    @Override
    public boolean supports(AfterSaleOrder order) {
        return "REFUND".equals(order.getType())
            && "PHYSICAL".equals(order.getProductType());
    }

    @Override
    public int getOrder() {
        return 10;
    }

    @Override
    public void process(AfterSaleOrder order) {
        // 1. 校验库存是否已出库
        validateWarehouseStatus(order);

        // 2. 计算退款金额(扣除运费)
        BigDecimal refundAmount = calculateRefundAmount(order);

        // 3. 调用支付通道退款
        paymentService.refund(order.getPaymentId(), refundAmount);

        // 4. 更新订单状态
        orderService.updateStatus(order.getId(), "REFUNDED");

        // 5. 发送通知
        notificationService.sendRefundSuccess(order.getUserId(), refundAmount);
    }
}

每个策略只管自己那一类售后,互不干扰。

策略的组合和顺序

有些场景下,多个策略需要组合执行。比如 APP 渠道的实物退款,要先执行渠道额外处理,再执行实物退款逻辑。

@Order@Autowired List 实现策略自动排序和收集:

@Service
public class AfterSaleStrategyFactory {

    private final List<AfterSaleStrategy> strategies;

    @Autowired
    public AfterSaleStrategyFactory(List<AfterSaleStrategy> strategies) {
        // Spring 会自动注入所有 AfterSaleStrategy 实现
        // 按 @Order 注解排序
        this.strategies = strategies.stream()
            .sorted(Comparator.comparingInt(AfterSaleStrategy::getOrder))
            .collect(Collectors.toList());
    }

    public void process(AfterSaleOrder order) {
        // 找到所有适用的策略
        List<AfterSaleStrategy> matched = strategies.stream()
            .filter(s -> s.supports(order))
            .collect(Collectors.toList());

        if (matched.isEmpty()) {
            throw new UnsupportedOperationException(
                "没有适用的售后处理策略: " + order);
        }

        // 按顺序执行
        for (AfterSaleStrategy strategy : matched) {
            strategy.process(order);
        }
    }
}

这样配置策略就变成了加一个类:

@Component
@Order(5)  // 比 RefundPhysicalProductStrategy(10) 先执行
public class AppChannelExtraStrategy implements AfterSaleStrategy {

    @Override
    public boolean supports(AfterSaleOrder order) {
        return "APP".equals(order.getChannel());
    }

    @Override
    public int getOrder() {
        return 5;
    }

    @Override
    public void process(AfterSaleOrder order) {
        // APP 渠道的额外日志埋点
        log.info("APP渠道售后单: {}", order.getId());
        // APP 特有的风控校验
        riskControlService.checkForApp(order);
    }
}

策略工厂的改进:支持动态策略

上面的实现依赖 Spring 注入,适合启动时就确定策略列表。但有些场景需要动态注册策略。

ConcurrentHashMap 实现动态策略注册:

@Service
public class DynamicAfterSaleStrategyFactory {

    private final Map<String, AfterSaleStrategy> strategyMap = new ConcurrentHashMap<>();

    public void registerStrategy(String code, AfterSaleStrategy strategy) {
        strategyMap.put(code, strategy);
    }

    public void unregisterStrategy(String code) {
        strategyMap.remove(code);
    }

    public void process(AfterSaleOrder order) {
        String key = generateKey(order);
        AfterSaleStrategy strategy = strategyMap.get(key);

        if (strategy == null) {
            throw new UnsupportedOperationException(
                "未找到售后策略: " + key);
        }

        strategy.process(order);
    }

    private String generateKey(AfterSaleOrder order) {
        return String.format("%s:%s:%s",
            order.getType(),
            order.getProductType(),
            order.getChannel());
    }
}

配合数据库配置,可以实现运营人员动态开启/关闭策略:

@Service
public class StrategyConfigService {

    @Autowired
    private StrategyConfigDao configDao;

    @Autowired
    private DynamicAfterSaleStrategyFactory factory;

    @PostConstruct
    public void loadStrategies() {
        List<StrategyConfig> configs = configDao.findActiveConfigs();
        for (StrategyConfig config : configs) {
            if ("PHYSICAL_REFUND".equals(config.getCode())) {
                factory.registerStrategy(
                    config.getCode(),
                    new RefundPhysicalProductStrategy()
                );
            }
            // 从数据库加载策略实现类
            // ... 更多策略
        }
    }
}

策略模式与模板方法的结合

有些策略有公共步骤,只是某一步不同。这时可以结合模板方法:

public abstract class AbstractAfterSaleStrategy implements AfterSaleStrategy {

    @Override
    public final void process(AfterSaleOrder order) {
        // 1. 前置校验(公共)
        preValidate(order);

        // 2. 执行业务逻辑(子类实现)
        doProcess(order);

        // 3. 后置处理(公共)
        postProcess(order);
    }

    protected void preValidate(AfterSaleOrder order) {
        // 校验售后单状态
        if (order.getStatus() != 1) {
            throw new BizException("售后单状态异常");
        }
        // 校验退款金额
        if (order.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
            throw new BizException("退款金额必须大于0");
        }
    }

    protected void postProcess(AfterSaleOrder order) {
        // 发送通知
        notificationService.sendAfterSaleNotice(order);
        // 记录操作日志
        operationLogService.log(order);
    }

    protected abstract void doProcess(AfterSaleOrder order);
}

子类只需关注核心逻辑:

@Component
public class RefundVirtualProductStrategy extends AbstractAfterSaleStrategy {

    @Override
    public boolean supports(AfterSaleOrder order) {
        return "REFUND".equals(order.getType())
            && "VIRTUAL".equals(order.getProductType());
    }

    @Override
    protected void doProcess(AfterSaleOrder order) {
        // 虚拟商品退款:直接退款,不涉及物流
        paymentService.refund(order.getPaymentId(), order.getAmount());
        orderService.updateStatus(order.getId(), "REFUNDED");
    }
}

策略模式的优缺点

优点:

  1. 消除 if-else:每个策略独立,代码清晰
  2. 方便扩展:加新策略只需加一个新类,不动现有代码
  3. 方便测试:每个策略单独测试,不依赖其他策略
  4. 支持动态配置:策略可以运行时注册/注销

缺点:

  1. 类数量增加:每个规则一个类,可能产生很多小类
  2. 策略需要共享数据时麻烦:如果策略间需要共享上下文,需要额外设计
  3. 策略选择逻辑:确定用哪个策略的逻辑可能变复杂

策略模式适用场景

不是所有 if-else 都要改成策略模式。当满足以下条件时,策略模式才是好的选择:

  1. 条件分支多:超过 3-4 个分支
  2. 每个分支逻辑复杂:不是简单赋值,而是多步骤处理
  3. 分支经常变化:经常加新规则或修改规则
  4. 分支有共同步骤:可以用模板方法提取公共流程

如果只是简单的赋值或转换,策略模式可能过度设计:

// 这种情况不需要策略模式
if ("A".equals(type)) {
    status = "已完成";
} else if ("B".equals(type)) {
    status = "进行中";
}
// 用 Map 更简单
private static final Map<String, String> STATUS_MAP = new HashMap<>();
static {
    STATUS_MAP.put("A", "已完成");
    STATUS_MAP.put("B", "进行中");
}

总结

策略模式把"选择算法"和"执行算法"分离,让代码更易维护和扩展。在售后处理这种复杂业务场景里效果明显:

  1. 每个售后类型、渠道、商品类型组合对应一个策略
  2. @Order 控制执行顺序
  3. 策略间有公共步骤时结合模板方法
  4. 需要动态配置时用 Map 注册机制

从 1000 行的 if-else 变成 20 个小策略类,每个策略类 50-100 行,改造成本可控,长期收益很高。

最后更新 4/20/2026, 4:48:48 AM