本文通过"业务算法动态切换不同数据源"的典型场景,系统介绍了策略模式的核心概念与 Spring Boot 落地实践。文章从模式定义、角色构成出发,给出了完整的多文件项目结构和可直接运行的代码示例,重点演示了如何利用 Spring 的依赖注入特性实现策略的自动注册与运行时切换,彻底消除 if-else 分支。同时展示了新增数据源时如何做到零侵入扩展,完美践行开闭原则,提供了一套清晰、优雅、可复用的设计模板。
一、策略模式(Strategy Pattern)介绍
定义:策略模式定义了一系列算法(或功能),将每个算法封装起来,并使它们可以相互替换。策略模式让算法的变化独立于使用它的客户端。
核心角色(对应你的案例):
- 抽象策略(Strategy) :定义统一的业务接口(如数据拉取方法
fetchData)。 - 具体策略(ConcreteStrategy) :实现接口的具体类(如
DBSource、ApiSource)。 - 上下文(Context) :持有策略引用并执行算法的类(如业务算法服务
AlgorithmContext)。
为什么用在这里(优点):
- 消除臃肿的 if-else :切换数据源通过类型字符串直接从
Map获取,业务逻辑内无任何条件分支。 - 开闭原则:新增第 3 种数据源(如 Redis)只需新增类加注解,业务算法和调用方零修改。
- 便于单元测试:可以轻松注入 Mock 策略类进行算法逻辑测试,无需依赖真实数据库或网络。
二、项目文件目录结构(Spring Boot 标准)
src/main/java/com/example/demo/
├── DemoApplication.java // Spring Boot 启动类
├── strategy/ // 策略包
│ ├── Strategy.java // 抽象策略接口
│ ├── DBSource.java // 具体策略A(数据库)
│ └── ApiSource.java // 具体策略B(外部API)
├── service/ // 业务服务包
│ └── AlgorithmContext.java // 上下文(核心业务算法)
└── controller/ // 控制器包
└── TestController.java // REST接口(接收前端切换参数)
三、完整代码实现(每个类单独文件)
1. DemoApplication.java(启动类)
java
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
2. strategy/Strategy.java(抽象策略接口)
java
package com.example.demo.strategy;
public interface Strategy {
// 统一的数据拉取契约
String fetchData(String key);
}
3. strategy/DBSource.java(数据库具体策略)
注意 :
@Component("db")指定 Bean 名称,Spring 会将其自动注册到策略 Map 中,Key 为"db"。
java
package com.example.demo.strategy;
import org.springframework.stereotype.Component;
@Component("db")
public class DBSource implements Strategy {
@Override
public String fetchData(String key) {
// 实际开发中此处放 JDBC / MyBatis / JPA 查询代码
return "【DB数据库】内容 for key: " + key;
}
}
4. strategy/ApiSource.java(API 接口具体策略)
java
package com.example.demo.strategy;
import org.springframework.stereotype.Component;
@Component("api")
public class ApiSource implements Strategy {
@Override
public String fetchData(String key) {
// 实际开发中此处放 RestTemplate / WebClient / HttpClient 调用
return "【API接口】内容 for key: " + key;
}
}
5. service/AlgorithmContext.java(上下文 / 核心业务算法)
核心逻辑 :Spring 会自动将所有
Strategy实现类注入到Map<String, Strategy>中。算法根据传入的type(如"db")直接获取策略,彻底消灭 if-else。
java
package com.example.demo.service;
import com.example.demo.strategy.Strategy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Map;
@Service
public class AlgorithmContext {
@Autowired
private Map<String, Strategy> strategyMap; // Key是@Component指定的名称,Value是策略实例
public String executeAlgorithm(String type, String key) {
// 1. 根据传入类型(如 "db")从Map中获取策略
Strategy strategy = strategyMap.get(type);
if (strategy == null) {
throw new IllegalArgumentException("未知的数据源类型: " + type);
}
// 2. 拉取原始数据(面向接口多态调用)
String rawData = strategy.fetchData(key);
// 3. 核心业务逻辑(假设:转大写 + 计算字符串长度)
String result = "【算法结果】" + rawData.toUpperCase() + ",长度=" + rawData.length();
// 4. 返回结果给调用方(Controller)
return result;
}
}
6. controller/TestController.java(RESTful 控制器)
前端只需传
type=db或type=api,即可动态切换数据源。
java
package com.example.demo.controller;
import com.example.demo.service.AlgorithmContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class TestController {
@Autowired
private AlgorithmContext algorithmContext;
@GetMapping("/compute")
public String compute(
@RequestParam String type, // 前端传入 "db" 或 "api"
@RequestParam String key // 查询键值
) {
return algorithmContext.executeAlgorithm(type, key);
}
}
四、运行测试
启动 Spring Boot 应用后,在浏览器或 Postman 中访问:
-
切换数据库源 :
GET http://localhost:8080/compute?type=db&key=user_001输出:
【算法结果】【DB数据库】内容 FOR KEY: USER_001,长度=25 -
切换 API 源 :
GET http://localhost:8080/compute?type=api&key=order_999输出:
【算法结果】【API接口】内容 FOR KEY: ORDER_999,长度=26
五、如何扩展第 3 种数据源(演示开闭原则)
假设未来要接入 Redis 数据源,无需修改任何已有代码:
- 新建
strategy/RedisSource.java:
java
package com.example.demo.strategy;
import org.springframework.stereotype.Component;
@Component("redis") // 新类型
public class RedisSource implements Strategy {
@Override
public String fetchData(String key) {
return "【Redis缓存】内容 for key: " + key;
}
}
- 重启应用 ,前端直接传
type=redis,AlgorithmContext和Controller一行代码都不用改,自动生效。