要优雅地使用策略模式来消除 if-else

很多老项目的代码,充斥着大量的 if-else 。
比如 if ("ALIPAY".equals(type)) { ... } else if ("WECHAT".equals(type)) { ... },这种代码不仅难看,而且每次新增一种支付方式都要修改主逻辑,违反了开闭原则 (OCP)

在 Spring Boot 中,利用 IOC 容器的自动注入特性,我们可以极其优雅地实现策略模式,完全消除 if-else

场景设定

我们需要处理订单支付,目前支持 支付宝 (AliPay)微信支付 (WeChatPay),未来可能会加 银联支付

原写法:

// 订单服务(客户代码:调用支付算法的模块)
public class OrderService {
    // 支付方法(客户代码直接耦合所有支付算法)
    public void pay(Order order, String payType) {
        // 客户代码中直接包含所有算法的判断和调用
        if ("WECHAT".equals(payType)) {
            // 微信支付算法(直接写在客户代码中,或直接调用具体类)
            System.out.println("微信支付:生成签名=" + order.getOrderNo() + ",扣减金额=" + order.getAmount());
            // 后续如果修改微信签名逻辑,必须改这里
        } else if ("ALIPAY".equals(payType)) {
            // 支付宝支付算法
            System.out.println("支付宝支付:生成订单号=" + order.getOrderNo() + ",扣减金额=" + order.getAmount());
        } else {
            throw new IllegalArgumentException("不支持的支付方式");
        }
    }
}

定义策略接口 (The Standard)

首先定义一个统一的接口,所有支付方式都要遵循这个标准。

public interface PaymentStrategy {
    /**
     * 核心支付逻辑
     * @param orderId 订单ID
     * @param amount 金额
     * @return 支付结果
     */
    String pay(Long orderId, double amount);

    /**
     * 策略标识(核心:用来区分是哪种策略)
     * @return 支付类型,如 "ALI_PAY", "WECHAT_PAY"
     */
    String getType();
}

具体策略实现 (The Components)

创建具体的支付实现类,并使用 @Component (或 @Service) 交给 Spring 管理。

策略 A:支付宝

@Component
public class AliPayStrategy implements PaymentStrategy {
    @Override
    public String pay(Long orderId, double amount) {
        // 这里对接支付宝SDK...
        return "支付宝支付成功:订单 " + orderId + ",金额 " + amount;
    }

    @Override
    public String getType() {
        return "ALI_PAY"; // 标识自己是支付宝
    }
}

策略 B:微信支付

@Component
public class WeChatPayStrategy implements PaymentStrategy {
    @Override
    public String pay(Long orderId, double amount) {
        // 这里对接微信SDK...
        return "微信支付成功:订单 " + orderId + ",金额 " + amount;
    }

    @Override
    public String getType() {
        return "WECHAT_PAY"; // 标识自己是微信
    }
}

策略工厂 (The Magic Factory) —— 核心步骤

下面这种写法是:工厂+策略的写法,如果需要多次复用策略建议采用此方法。

这是最能体现 Spring 优雅的地方。我们不需要手动 new AliPayStrategy(),而是让 Spring 帮我们把所有实现了 PaymentStrategy 接口的 Bean 自动注入到一个 Map 中。

@Component
public class PaymentFactory {

    // 【核心魔法】:Spring 会自动把所有 PaymentStrategy 的实现类注入到这个 Map 中
    // key = Bean 的名称(默认类名首字母小写),value = 对应的 Bean 对象
    // 但为了更灵活,我们通常配合 @PostConstruct 自己构建一个 Map
    
    private final Map<String, PaymentStrategy> strategyMap = new ConcurrentHashMap<>();

    // 构造函数注入所有的 Strategy 实现类 (List形式)
    // 这样是可以做到的,详见下方链接
    private final List<PaymentStrategy> strategyList;

    public PaymentFactory(List<PaymentStrategy> strategyList) {
        this.strategyList = strategyList;
    }

    // 在 Bean 初始化完成后,自动执行该方法,构建查找表
    @PostConstruct
    private void init() {
        for (PaymentStrategy strategy : strategyList) {
            // key 使用我们在接口中定义的 getType(),比用 BeanName 更可控
            strategyMap.put(strategy.getType(), strategy);
        }
    }

    // 对外提供的方法:根据 type 获取对应的策略
    public PaymentStrategy getStrategy(String type) {
        PaymentStrategy strategy = strategyMap.get(type);
        if (strategy == null) {
            throw new IllegalArgumentException("暂不支持该支付方式: " + type);
        }
        return strategy;
    }
}

所有实现类对象的依赖注入

  • 如果不依赖 Map,也能实现抛弃 if-else 的写法:

这种方法甚至没用到简单工厂模式,是原汁原味的策略模式。即不返回策略对象,而是直接调用策略方法

// 1. 抽象策略接口(定义支付算法的统一规范,客户代码只依赖该接口)
public interface PaymentStrategy {
    void pay(Order order);
}

// 2. 具体策略类(封装各自的支付算法,算法变化只修改这里)
// 微信支付策略
public class WeChatPaymentStrategy implements PaymentStrategy {
    @Override
    public void pay(Order order) {
        System.out.println("微信支付:生成签名=" + order.getOrderNo() + ",扣减金额=" + order.getAmount());
    }
}

// 支付宝支付策略
public class AlipayPaymentStrategy implements PaymentStrategy {
    @Override
    public void pay(Order order) {
        System.out.println("支付宝支付:生成订单号=" + order.getOrderNo() + ",扣减金额=" + order.getAmount());
    }
}

// 3. 环境类(客户代码:持有抽象策略接口引用,不依赖任何具体算法)
public class OrderService {
    // 客户代码只依赖抽象策略接口,不关心具体实现
    public void pay(Order order, PaymentStrategy paymentStrategy) {
        // 调用算法:只调用接口方法,无任何具体算法逻辑
        paymentStrategy.pay(order);
    }
}

话说,像这样,我单纯只使用策略模式,也能做到去掉if-else的效果啊,为什么要多此一举弄个工厂呢
见:纯策略 VS 工厂+策略

业务调用 (Usage)

现在,在我们的 Controller 或 Service 中,代码会变得极其干净。

@RestController
@RequestMapping("/pay")
public class PayController {

    @Autowired
    private PaymentFactory paymentFactory;

    @PostMapping
    public String pay(@RequestParam String type, 
                      @RequestParam Long orderId, 
                      @RequestParam double amount) {
        
        // 1. 一行代码获取对应策略,完全没有 if-else
        PaymentStrategy strategy = paymentFactory.getStrategy(type);
        
        // 2. 执行支付
        return strategy.pay(orderId, amount);
    }
}

为什么说这很优雅?

  1. 消除了 if-else:主逻辑(Factory 和 Controller)中看不到任何条件判断。
  2. 符合开闭原则 (OCP)
    如果你明天要加一个“银联支付”,你只需要:
    • 新建一个 UnionPayStrategy 实现接口。
    • 加上 @Component 注解。
    • 其他代码(Factory、Controller)一行都不用改! Spring 会自动扫描到新 Bean 并加入 List,工厂自动注册。
  3. 类型安全:通过 getType() 方法(甚至可以使用 Enum 代替 String)来管理类型,比直接依懒 Bean 的名称(aliPayStrategy)更健壮。

还可以更高级吗?

变种:利用枚举 (Enum) 结合

如果你的业务类型是固定的枚举,可以将策略逻辑直接绑定在枚举中(适合逻辑简单的场景),或者让工厂 Map 的 Key 变成枚举。

// 使用 Enum 作为 Map 的 Key,更加类型安全
private final Map<PaymentTypeEnum, PaymentStrategy> strategyMap = ...
变种:模版方法 + 策略

如果所有支付流程都大致相同(验签 -> 核心支付 -> 记日志),你可以搞一个 AbstractPaymentStrategy 实现公共逻辑,只把差异化的核心支付逻辑留给子类,这就是 模板方法模式 + 策略模式 的混合双打。

下一步建议

如果你想挑战更有难度的场景,比如“多个策略同时生效”(例如:搞活动时,既要打折策略,又要积分策略),这种应该怎么设计?

见:管道模式

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐