Spring Boot 中使用策略模式优化多分支业务逻辑

作者:old wang 发布时间: 2025-02-22 阅读量:3 评论数:0

在业务系统中,经常会遇到这样一种场景:

同一个接口入口,根据请求中的某个字段,走不同的业务处理逻辑。

比如:

  • 第三方 OA 系统回调;

  • 支付渠道回调;

  • 消息通知分发;

  • 文件类型处理;

  • 不同业务类型的审批流程;

  • 不同来源的数据同步逻辑。

最直接的写法,可能是在一个方法里写大量 if...else if...

例如:

if ("add".equals(type)) {
    // 加法逻辑
} else if ("subtract".equals(type)) {
    // 减法逻辑
} else if ("multiple".equals(type)) {
    // 乘法逻辑
} else if ("division".equals(type)) {
    // 除法逻辑
}

这种写法在业务类型较少时问题不大。

但随着业务分支越来越多,这个方法会越来越臃肿:

  1. 新增一种业务类型,就要修改原来的判断逻辑;

  2. 不同业务逻辑混在一个类里,可读性变差;

  3. 单元测试不方便;

  4. 后期扩展容易影响已有逻辑;

  5. 代码不符合开闭原则。

这种场景就比较适合使用策略模式。

一、什么是策略模式

策略模式的核心思想是:

定义一组算法或业务处理逻辑,把它们分别封装起来,并让它们可以相互替换。

在实际业务中,可以理解为:

  • 一个公共接口定义统一行为;

  • 每个业务分支实现这个接口;

  • 根据请求参数选择对应的实现类执行。

这样做之后,新增一个业务分支时,只需要新增一个策略实现类,不需要修改原来的主流程代码。

二、示例场景

这里用一个简单的计算器示例说明。

假设系统提供一个接口,根据传入的操作类型执行不同计算逻辑:

add       -> 加法
subtract  -> 减法
multiple  -> 乘法
division  -> 除法

在真实业务中,这里的加减乘除可以替换成:

oa_approval_callback
payment_callback
sms_callback
email_callback
order_sync

示例只是为了简化说明,重点在于“根据类型路由到不同业务处理类”。

三、定义策略接口

首先定义一个统一的策略接口。

public interface CalculationStrategy {

    /**
     * 执行计算逻辑
     */
    int operate(int num1, int num2);
}

所有具体业务分支都实现这个接口。

这样调用方只需要面向接口编程,不需要关心具体执行的是哪个实现类。

四、定义具体策略实现类

1. 加法策略

import org.springframework.stereotype.Component;

@Component("add")
public class AddCalculationStrategy implements CalculationStrategy {

    @Override
    public int operate(int num1, int num2) {
        return num1 + num2;
    }
}

2. 减法策略

import org.springframework.stereotype.Component;

@Component("subtract")
public class SubtractionCalculationStrategy implements CalculationStrategy {

    @Override
    public int operate(int num1, int num2) {
        return num1 - num2;
    }
}

3. 乘法策略

import org.springframework.stereotype.Component;

@Component("multiple")
public class MultiplicationCalculationStrategy implements CalculationStrategy {

    @Override
    public int operate(int num1, int num2) {
        return num1 * num2;
    }
}

4. 除法策略

import org.springframework.stereotype.Component;

@Component("division")
public class DivisionCalculationStrategy implements CalculationStrategy {

    @Override
    public int operate(int num1, int num2) {
        return num1 / num2;
    }
}

这里需要注意:

@Component("add")

括号里的值就是 Spring 容器中的 Bean 名称。

后面可以通过这个名称,从策略集合中找到对应的实现类。

如果不指定名称,Spring 默认会使用类名首字母小写作为 Bean 名称。

例如:

@Component
public class TestStrategyImpl implements CalculationStrategy {
    // ...
}

默认 Bean 名称就是:

testStrategyImpl

五、定义策略工厂

接下来定义一个策略工厂,用来保存所有策略实现类。

import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.Map;

@Component
public class CalculationFactory {

    private final Map<String, CalculationStrategy> calculationStrategyMap = new HashMap<>();

    public CalculationFactory(Map<String, CalculationStrategy> strategyMap) {
        this.calculationStrategyMap.clear();
        this.calculationStrategyMap.putAll(strategyMap);
    }

    public CalculationStrategy getStrategy(String strategy) {
        return calculationStrategyMap.get(strategy);
    }
}

这里的关键点是构造方法:

public CalculationFactory(Map<String, CalculationStrategy> strategyMap) {
    this.calculationStrategyMap.clear();
    this.calculationStrategyMap.putAll(strategyMap);
}

Spring 在启动时,会自动把所有实现了 CalculationStrategy 接口的 Bean 注入进来。

注入后的 Map 结构大致如下:

key      -> Bean 名称
value    -> 对应的策略实现类

例如:

add       -> AddCalculationStrategy
subtract  -> SubtractionCalculationStrategy
multiple  -> MultiplicationCalculationStrategy
division  -> DivisionCalculationStrategy

这样就可以根据请求参数动态选择策略。

六、在 Service 中执行策略

业务层根据传入的策略类型,从工厂中获取对应的策略实现类。

import org.springframework.stereotype.Service;

@Service
public class CalculationService {

    private final CalculationFactory calculationFactory;

    public CalculationService(CalculationFactory calculationFactory) {
        this.calculationFactory = calculationFactory;
    }

    public int operateByStrategy(String strategy, int num1, int num2) {
        CalculationStrategy calculationStrategy = calculationFactory.getStrategy(strategy);

        if (calculationStrategy == null) {
            throw new IllegalArgumentException("不支持的计算类型:" + strategy);
        }

        return calculationStrategy.operate(num1, num2);
    }
}

这里一定要做空值判断。

如果传入了一个不存在的策略类型,例如:

mod

那么:

calculationFactory.getStrategy(strategy)

会返回 null

如果不判断,后面直接调用:

calculationStrategy.operate(num1, num2)

就会出现 NullPointerException

所以这里需要根据业务场景做兜底处理,比如:

  • 抛出业务异常;

  • 返回默认值;

  • 记录日志;

  • 走默认策略。

本文示例中直接抛出异常:

throw new IllegalArgumentException("不支持的计算类型:" + strategy);

七、提供测试接口

最后写一个简单的 Controller 测试接口。

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/strategy")
public class TestStrategyController {

    private final CalculationService calculationService;

    public TestStrategyController(CalculationService calculationService) {
        this.calculationService = calculationService;
    }

    @GetMapping("/test/{operation}/{num1}/{num2}")
    public int testCalculation(@PathVariable String operation,
                               @PathVariable int num1,
                               @PathVariable int num2) {
        return calculationService.operateByStrategy(operation, num1, num2);
    }
}

请求示例:

GET /strategy/test/add/10/2

返回:

12

请求:

GET /strategy/test/subtract/10/2

返回:

8

请求:

GET /strategy/test/multiple/10/2

返回:

20

请求:

GET /strategy/test/division/10/2

返回:

5

八、这种写法的扩展方式

如果后续需要新增一种运算,比如取模:

mod

只需要新增一个策略实现类:

import org.springframework.stereotype.Component;

@Component("mod")
public class ModCalculationStrategy implements CalculationStrategy {

    @Override
    public int operate(int num1, int num2) {
        return num1 % num2;
    }
}

其他代码不需要改。

不用修改:

CalculationService

也不用修改:

CalculationFactory

更不用在原来的业务方法中继续追加:

else if ("mod".equals(type)) {
    // ...
}

这就是策略模式在业务扩展中的价值。

九、适合使用策略模式的场景

策略模式不是为了“炫技”,而是为了解决多分支业务扩展问题。

比较适合的场景有:

1. 根据类型执行不同业务逻辑

例如:

订单类型
支付渠道
消息类型
文件类型
审批类型
回调类型

2. 分支逻辑相对独立

每个分支有自己的业务处理过程,彼此之间没有太强的耦合。

这种情况下拆成多个策略类,会比放在一个大方法里更清晰。

3. 后续类型可能持续增加

如果业务类型经常新增,策略模式可以减少对原有代码的修改。

新增策略类即可完成扩展。

4. 希望不同策略可以独立测试

每个策略实现类都是独立 Bean,可以单独写单元测试。

这比测试一个包含大量 if...else 的方法更方便。

十、不适合使用策略模式的场景

策略模式也不是所有场景都适合。

如果业务只有两三个简单分支,而且长期不会变化,直接写 if...else 反而更简单。

例如:

if (enabled) {
    doA();
} else {
    doB();
}

这种代码没有必要强行拆成策略模式。

如果为了设计模式而设计模式,反而会增加代码复杂度。

所以是否使用策略模式,可以看三个条件:

  1. 分支是否较多;

  2. 分支逻辑是否复杂;

  3. 后续是否经常扩展。

如果这三个条件都满足,就比较适合用策略模式。

十一、和简单工厂的关系

本文示例中,CalculationFactory 实际上承担了简单工厂的职责。

它负责根据策略标识找到具体的策略实现类。

整体结构可以理解为:

Controller
    -> Service
        -> Factory
            -> Strategy

其中:

  • Strategy 定义统一接口;

  • 具体策略类实现不同业务逻辑;

  • Factory 负责根据 key 找到对应策略;

  • Service 负责组织业务流程;

  • Controller 负责接收请求。

在 Spring Boot 中,由于 Spring 容器本身已经帮我们管理了 Bean,所以策略注册过程可以交给 Spring 自动完成。

这也是 Spring Boot 中使用策略模式比较方便的原因。

十二、完整调用流程

以请求:

GET /strategy/test/add/10/2

为例,完整流程如下:

1. Controller 接收 operation = add
2. Controller 调用 CalculationService
3. Service 调用 CalculationFactory.getStrategy("add")
4. Factory 从 Map 中取出 add 对应的策略实现类
5. 执行 AddCalculationStrategy.operate(10, 2)
6. 返回计算结果 12

如果请求的是:

GET /strategy/test/multiple/10/2

则会执行:

MultiplicationCalculationStrategy.operate(10, 2)

主流程代码不需要关心具体执行的是哪个类。

它只需要面向接口:

CalculationStrategy

即可。

十三、这种写法的优点

1. 消除大量 if...else

原来所有分支都集中在一个方法里。

使用策略模式后,每个分支变成一个独立类。

代码结构更清晰。

2. 扩展更方便

新增业务类型时,只需要新增一个策略实现类。

不需要修改原来的判断逻辑。

3. 更符合开闭原则

对扩展开放,对修改关闭。

已有逻辑不需要频繁改动,可以降低改出问题的概率。

4. 更适合复杂业务

如果每个分支内部逻辑都很复杂,拆成多个策略类后,每个类只关注自己的业务。

代码更容易维护。

5. 更方便测试

每个策略类都可以单独测试。

不会因为一个大方法里分支太多,导致测试用例难写。

十四、需要注意的问题

1. 策略 key 要统一管理

示例中直接使用字符串:

@Component("add")

业务简单时问题不大。

但在真实项目中,如果 key 比较多,建议统一定义常量。

例如:

public interface CalculationStrategyType {

    String ADD = "add";

    String SUBTRACT = "subtract";

    String MULTIPLE = "multiple";

    String DIVISION = "division";
}

这样可以避免字符串写错。

2. 要处理找不到策略的情况

不要直接:

calculationFactory.getStrategy(strategy).operate(num1, num2);

因为策略不存在时会空指针。

建议统一处理:

CalculationStrategy calculationStrategy = calculationFactory.getStrategy(strategy);

if (calculationStrategy == null) {
    throw new IllegalArgumentException("不支持的策略类型:" + strategy);
}

3. 策略类不要承载过多职责

每个策略类只处理一种业务逻辑。

不要把新的 if...else 又写进策略类里。

否则只是把复杂度从一个地方挪到了另一个地方。

4. 注意 Bean 名称大小写

示例中的策略名称依赖 @Component 中的 Bean 名称。

例如:

@Component("division")

调用时也要传:

division

如果写成:

Division

就可能找不到对应策略。

所以建议策略 key 使用统一的小写格式。

结论

策略模式适合解决多分支业务逻辑扩展问题。

在 Spring Boot 中,可以通过下面这种方式实现:

  1. 定义一个策略接口;

  2. 每个业务分支实现该接口;

  3. 使用 @Component("策略标识") 注册策略;

  4. 通过构造方法注入 Map<String, Strategy>

  5. 根据请求参数从 Map 中选择对应策略执行。

这种写法可以减少大量 if...else,让业务分支更加独立,也方便后续扩展。

评论