在 Spring Boot 中,我们可以利用其强大的依赖注入(DI)和组件扫描(Component Scan)特性,非常优雅地实现和管理策略模式。
核心思想:
- 定义策略接口(Strategy Interface): 定义一个所有具体策略类都必须实现的公共接口。这个接口通常包含一个或多个方法,代表策略要执行的操作。
- 创建具体策略实现(Concrete Strategies): 实现策略接口,每个实现类代表一种具体的实现行为。
- 创建上下文(Context): 持有一个策略接口的引用。上下文不直接实现业务逻辑,而是委托给其持有的策略对象。上下文提供一个方法让客户端设置或改变策略,并提供一个执行策略的方法。
- 客户端(Client): 创建具体的策略对象,并将其设置到上下文中。然后调用上下文的方法来执行策略。
在 Spring Boot 中的实现优势:
- 自动发现和注册: 使用
@Component
或其衍生注解(@Service
,@Repository
等),Spring 可以自动扫描并注册所有的具体策略实现到 IoC 容器中。 - 依赖注入: 上下文或其他需要使用策略的服务可以通过
@Autowired
或构造函数注入的方式,轻松获取所有或特定的策略实现。 - 解耦: Spring 的 DI 机制进一步加强了策略模式的解耦特性。上下文不需要知道如何创建策略对象,只需要声明对策略接口的依赖。
- 易于扩展: 添加新的策略只需要创建一个新的实现类并标记为 Spring Bean,无需修改现有代码(符合开闭原则)。
实现步骤详解 (附带示例):
假设我们要实现一个订单处理系统,根据不同的订单类型(普通订单、促销订单、会员订单)有不同的处理逻辑(例如计算折扣、积分等)。
步骤 1: 定义策略接口
package com.example.strategydemo.strategy;
// 策略接口:定义订单处理逻辑
public interface OrderHandlerStrategy {
/**
* 判断当前策略是否适用于给定的订单类型
* @param orderType 订单类型标识 ("NORMAL", "PROMOTION", "VIP")
* @return true 如果适用, false 否则
*/
boolean supports(String orderType);
/**
* 处理订单的具体逻辑
* @param orderId 订单ID
* @return 处理结果描述
*/
String handleOrder(Long orderId);
}
supports(String orderType)
: 这个方法用于标识哪个策略实现应该处理特定类型的请求。handleOrder(Long orderId)
: 这是策略的核心执行方法。
步骤 2: 创建具体策略实现
为每种订单类型创建一个实现类,并使用 @Component
或 @Service
注解标记为 Spring Bean。
package com.example.strategydemo.strategy.impl;
import com.example.strategydemo.strategy.OrderHandlerStrategy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component; // 或者 @Service
@Component // 标记为 Spring Bean
public class NormalOrderHandler implements OrderHandlerStrategy {
private static final Logger log = LoggerFactory.getLogger(NormalOrderHandler.class);
private static final String ORDER_TYPE = "NORMAL";
@Override
public boolean supports(String orderType) {
return ORDER_TYPE.equalsIgnoreCase(orderType);
}
@Override
public String handleOrder(Long orderId) {
log.info("Processing normal order with ID: {}", orderId);
// ... 普通订单的具体处理逻辑 ...
return "Normal order " + orderId + " processed successfully.";
}
}
package com.example.strategydemo.strategy.impl;
import com.example.strategydemo.strategy.OrderHandlerStrategy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component
public class PromotionOrderHandler implements OrderHandlerStrategy {
private static final Logger log = LoggerFactory.getLogger(PromotionOrderHandler.class);
private static final String ORDER_TYPE = "PROMOTION";
@Override
public boolean supports(String orderType) {
return ORDER_TYPE.equalsIgnoreCase(orderType);
}
@Override
public String handleOrder(Long orderId) {
log.info("Processing promotion order with ID: {}", orderId);
// ... 促销订单的具体处理逻辑 (应用折扣) ...
return "Promotion order " + orderId + " processed with discount.";
}
}
package com.example.strategydemo.strategy.impl;
import com.example.strategydemo.strategy.OrderHandlerStrategy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component
public class VipOrderHandler implements OrderHandlerStrategy {
private static final Logger log = LoggerFactory.getLogger(VipOrderHandler.class);
private static final String ORDER_TYPE = "VIP";
@Override
public boolean supports(String orderType) {
return ORDER_TYPE.equalsIgnoreCase(orderType);
}
@Override
public String handleOrder(Long orderId) {
log.info("Processing VIP order with ID: {}", orderId);
// ... VIP 订单的具体处理逻辑 (增加积分) ...
return "VIP order " + orderId + " processed with extra points.";
}
}
步骤 3: 创建上下文或策略选择器 (关键)
这是将策略模式与 Spring Boot 结合的核心部分。我们不需要手动创建和设置策略,而是利用 Spring 注入所有策略,并根据条件选择合适的策略。
方法一:注入 List,手动查找
package com.example.strategydemo.service;
import com.example.strategydemo.strategy.OrderHandlerStrategy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import jakarta.annotation.PostConstruct; // Javax -> Jakarta in Spring Boot 3+
import java.util.List;
import java.util.Optional;
@Service
public class OrderService {
// Spring 会自动注入所有 OrderHandlerStrategy 接口的实现 Bean
private final List<OrderHandlerStrategy> orderHandlers;
@Autowired
public OrderService(List<OrderHandlerStrategy> orderHandlers) {
this.orderHandlers = orderHandlers;
System.out.println("发现 " + orderHandlers.size() + " 个订单处理策略。");
}
// 可选: 打印已加载的策略 (用于调试)
@PostConstruct
public void init() {
orderHandlers.forEach(handler ->
System.out.println("Loaded Order Handler: " + handler.getClass().getSimpleName())
);
}
public String handleOrder(Long orderId, String orderType) {
// 查找支持该订单类型的策略
Optional<OrderHandlerStrategy> handlerOpt = orderHandlers.stream()
.filter(handler -> handler.supports(orderType))
.findFirst();
if (handlerOpt.isPresent()) {
// 找到策略,执行处理
return handlerOpt.get().handleOrder(orderId);
} else {
// 没有找到合适的策略
throw new IllegalArgumentException("No handler found for order type: " + orderType);
// 或者返回默认处理/错误信息
// return "Unsupported order type: " + orderType;
}
}
}
方法二:注入 Map (更推荐,性能更好)
我们可以利用 Spring 将同一接口的所有 Bean 注入到一个 Map 中,其中 Key 是 Bean 的名称,Value 是 Bean 实例。为了更方便地按 orderType
查找,我们可以稍微调整一下,或者在 PostConstruct
中构建一个更合适的查找 Map。
方案 A: 使用默认 Bean Name (类名首字母小写) - 不太适合按 orderType
查找。
方案 B: 结合 supports
方法构建查找 Map (推荐)
package com.example.strategydemo.service;
import com.example.strategydemo.strategy.OrderHandlerStrategy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import jakarta.annotation.PostConstruct;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@Service
public class OrderServiceWithMap {
private final List<OrderHandlerStrategy> handlerList; // 仍然注入 List
private final Map<String, OrderHandlerStrategy> handlerMap = new HashMap<>(); // 用于快速查找的 Map
@Autowired
public OrderServiceWithMap(List<OrderHandlerStrategy> handlerList) {
this.handlerList = handlerList;
}
// 在 Bean 初始化后执行, 构建查找 Map
@PostConstruct
public void initializeHandlerMap() {
for (OrderHandlerStrategy handler : handlerList) {
if (handler.supports("NORMAL")) {
handlerMap.put("NORMAL", handler);
} else if (handler.supports("PROMOTION")) {
handlerMap.put("PROMOTION", handler);
} else if (handler.supports("VIP")) {
handlerMap.put("VIP", handler);
}
// ... 如果有更多类型需要这样添加
System.out.println("Mapped handler for type: " + getKeyForHandler(handler) + " -> " + handler.getClass().getSimpleName());
}
System.out.println("订单处理策略 Map 初始化完成: " + handlerMap.keySet());
}
// 辅助方法 (仅用于上面的示例打印)
private String getKeyForHandler(OrderHandlerStrategy handler) {
if (handler.supports("NORMAL")) return "NORMAL";
if (handler.supports("PROMOTION")) return "PROMOTION";
if (handler.supports("VIP")) return "VIP";
return "UNKNOWN";
}
public String handleOrder(Long orderId, String orderType) {
OrderHandlerStrategy handler = handlerMap.get(orderType.toUpperCase()); // 直接通过 Map 查找
if (handler != null) {
return handler.handleOrder(orderId);
} else {
throw new IllegalArgumentException("No handler found for order type: " + orderType);
}
}
}
方案 C: (Spring 4+) 直接注入 Map<String, YourInterface>
Spring 可以直接将 Bean Name 作为 Key 注入 Map。如果你给 Bean 命名(例如 @Component("normalOrderHandler")
),那么 Key 就是 “normalOrderHandler”。如果你想用 orderType
(“NORMAL”, "VIP"等) 作为 Key,需要结合其他方式,比如上面的 @PostConstruct
方法,或者更高级的自定义 Bean Factory Post Processor。直接注入 Map 对按 Bean Name 查找很方便,但按业务逻辑 Key (orderType) 查找通常需要额外处理。
步骤 4: 客户端调用
客户端可以是 Controller、另一个 Service,或者测试类。它只需要注入 OrderService
(或 OrderServiceWithMap
) 并调用其方法。
package com.example.strategydemo.controller;
import com.example.strategydemo.service.OrderService; // 或 OrderServiceWithMap
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class OrderController {
private final OrderService orderService; // 注入处理服务
@Autowired
public OrderController(OrderService orderService) { // 使用构造函数注入
this.orderService = orderService;
}
@GetMapping("/order/{orderId}")
public String processOrder(@PathVariable Long orderId, @RequestParam String type) {
try {
// 客户端只需要提供订单ID和类型,不需要关心具体是哪个策略在执行
return orderService.handleOrder(orderId, type);
} catch (IllegalArgumentException e) {
return "Error: " + e.getMessage();
}
}
}
运行与测试:
- 确保你的 Spring Boot 应用配置了组件扫描(通常
@SpringBootApplication
默认包含)。 - 运行应用。
- 访问 API:
GET /order/101?type=NORMAL
-> 返回普通订单处理结果GET /order/102?type=PROMOTION
-> 返回促销订单处理结果GET /order/103?type=VIP
-> 返回 VIP 订单处理结果GET /order/104?type=UNKNOWN
-> 返回错误信息或默认处理
总结与优势:
- 高内聚,低耦合: 每个策略类只关注自己的算法实现。上下文(
OrderService
)与具体策略解耦,只依赖于策略接口。 - 易于扩展: 添加新的订单处理类型(如 “GROUPON” 团购订单)?只需:
- 创建一个新的
GroupOnOrderHandler
实现OrderHandlerStrategy
接口。 - 用
@Component
标记它。 - 如果使用了 Map 方式,确保
initializeHandlerMap
能正确地将其添加到 Map 中(最好是通过统一的getType()
方法或注解)。 - 无需修改
OrderService
的核心查找逻辑(特别是使用 Map 时)或 Controller。
- 创建一个新的
- 符合开闭原则: 对扩展开放(可以添加新策略),对修改封闭(不需要修改现有策略或上下文的核心逻辑)。
- 代码更清晰: 避免了庞大的
if-else
或switch
语句,逻辑分布到各个策略类中,更易于理解和维护。 - 易于测试: 可以独立测试每个策略类。也可以 Mock 策略接口来测试上下文(
OrderService
)。 - 利用 Spring 生态: 完美结合 Spring DI 和组件扫描,代码简洁优雅。
注意事项:
- 策略选择逻辑: 如何选择正确的策略是关键。
supports()
方法、getType()
方法或注解配合 Map 构建是常用且高效的方式。要确保选择逻辑清晰且不会导致冲突(例如,一个orderType
不应被多个策略support
)。 - 状态管理: 策略对象通常应该是无状态的(Stateless),或者其状态不应影响其他请求的处理。如果策略需要状态,考虑将策略 Bean 的作用域(Scope)设置为
prototype
而不是默认的singleton
,并在每次需要时获取新的实例(但这会增加复杂性,尽量设计为无状态)。 - 过度设计: 对于只有两三种简单策略且不常变化的情况,策略模式可能有点过度设计。但随着业务复杂度增加,其优势会非常明显。