我项目里的策略模式:从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");
}
}
策略模式的优缺点
优点:
- 消除 if-else:每个策略独立,代码清晰
- 方便扩展:加新策略只需加一个新类,不动现有代码
- 方便测试:每个策略单独测试,不依赖其他策略
- 支持动态配置:策略可以运行时注册/注销
缺点:
- 类数量增加:每个规则一个类,可能产生很多小类
- 策略需要共享数据时麻烦:如果策略间需要共享上下文,需要额外设计
- 策略选择逻辑:确定用哪个策略的逻辑可能变复杂
策略模式适用场景
不是所有 if-else 都要改成策略模式。当满足以下条件时,策略模式才是好的选择:
- 条件分支多:超过 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", "进行中");
}
总结
策略模式把"选择算法"和"执行算法"分离,让代码更易维护和扩展。在售后处理这种复杂业务场景里效果明显:
- 每个售后类型、渠道、商品类型组合对应一个策略
- 用
@Order控制执行顺序 - 策略间有公共步骤时结合模板方法
- 需要动态配置时用 Map 注册机制
从 1000 行的 if-else 变成 20 个小策略类,每个策略类 50-100 行,改造成本可控,长期收益很高。
