1. 策略模式入门:为什么你的Java项目需要它?
我刚入行Java开发时,最头疼的就是遇到那种满屏if-else的业务逻辑。记得第一次接手一个电商促销模块,各种优惠类型(满减、折扣、赠品)的判断条件层层嵌套,代码维护起来简直是一场噩梦。直到后来系统学习了策略模式,才发现原来这类问题早有优雅的解决方案。
策略模式(Strategy Pattern)属于行为型设计模式,它定义了一系列算法,并将每个算法封装起来,使它们可以相互替换。这种模式最大的优势在于算法的变化独立于使用算法的客户端。在实际项目中,当遇到以下场景时,就该考虑使用策略模式了:
- 系统需要在多种算法中选择一种(如支付方式、文件解析策略)
- 需要避免使用多重条件判断语句(特别是当条件判断超过3层时)
- 需要动态切换算法或行为(如游戏中的角色技能切换)
提示:判断是否该用策略模式有个简单标准 - 如果你发现自己在频繁修改某个类的代码只是为了增加新的处理逻辑,那么这个类很可能需要策略模式重构。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 策略模式核心原理与UML解析
2.1 模式结构拆解
策略模式的核心由三个角色组成:
-
Context(环境类):持有一个Strategy的引用,负责与客户端交互。它不关心具体策略实现,只通过统一接口调用策略。
-
Strategy(抽象策略):定义所有支持的算法的公共接口,通常是一个接口或抽象类。
-
ConcreteStrategy(具体策略):实现了Strategy接口的具体算法类。
java复制// 典型策略模式UML对应的Java代码
interface Strategy {
void algorithm();
}
class ConcreteStrategyA implements Strategy {
@Override
public void algorithm() {
System.out.println("使用算法A");
}
}
class Context {
private Strategy strategy;
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
public void executeStrategy() {
strategy.algorithm();
}
}
2.2 与状态模式的本质区别
很多初学者容易混淆策略模式和状态模式,它们的UML图确实相似,但设计意图完全不同:
- 策略模式:客户端主动选择具体策略,策略之间通常无状态关联
- 状态模式:状态转换由内部条件触发,状态之间往往存在转移关系
举个例子,支付方式选择是策略模式(用户主动选择支付宝/微信),而订单状态流转是状态模式(系统根据条件自动从"待支付"变为"已支付")。
3. 从零实现策略模式完整示例
3.1 电商促销场景实战
假设我们要实现一个电商促销系统,支持多种优惠计算方式。先看传统if-else的实现:
java复制// 反面教材:使用条件判断的促销计算
public BigDecimal calculatePrice(String promoType, BigDecimal price) {
if ("FULL_REDUCTION".equals(promoType)) {
// 满减逻辑
return price.compareTo(THRESHOLD) > 0 ? price.subtract(DISCOUNT) : price;
} else if ("DISCOUNT".equals(promoType)) {
// 折扣逻辑
return price.multiply(DISCOUNT_RATE);
} else if ("GIFT".equals(promoType)) {
// 赠品逻辑
return price;
}
throw new IllegalArgumentException("不支持的促销类型");
}
这种实现的问题很明显:每次新增促销类型都要修改这个方法,违反开闭原则。下面用策略模式重构:
java复制// 1. 定义策略接口
public interface PromotionStrategy {
BigDecimal applyPromotion(BigDecimal price);
}
// 2. 实现具体策略
public class FullReductionStrategy implements PromotionStrategy {
@Override
public BigDecimal applyPromotion(BigDecimal price) {
return price.compareTo(THRESHOLD) > 0 ? price.subtract(DISCOUNT) : price;
}
}
// 3. 环境类
public class PromotionContext {
private PromotionStrategy strategy;
public PromotionContext(PromotionStrategy strategy) {
this.strategy = strategy;
}
public BigDecimal executeStrategy(BigDecimal price) {
return strategy.applyPromotion(price);
}
}
// 客户端调用
PromotionContext context = new PromotionContext(new FullReductionStrategy());
BigDecimal finalPrice = context.executeStrategy(originalPrice);
3.2 结合Spring的优雅实现
在实际企业级开发中,我们通常会用Spring框架来管理策略对象。下面是更工程化的实现:
java复制// 1. 使用注解标记策略实现类
@Service
@PromotionType("FULL_REDUCTION")
public class FullReductionStrategy implements PromotionStrategy {
// 实现同上
}
// 2. 创建策略工厂
@Component
public class PromotionStrategyFactory {
@Autowired
private Map<String, PromotionStrategy> strategyMap;
public PromotionStrategy getStrategy(String type) {
return strategyMap.values().stream()
.filter(s -> s.getClass().getAnnotation(PromotionType.class).value().equals(type))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("未知促销类型"));
}
}
// 3. 客户端调用
@RestController
public class PromotionController {
@Autowired
private PromotionStrategyFactory factory;
@PostMapping("/calculate")
public BigDecimal calculate(@RequestParam String type, @RequestParam BigDecimal price) {
PromotionStrategy strategy = factory.getStrategy(type);
return strategy.applyPromotion(price);
}
}
这种实现利用了Spring的依赖注入特性,通过自定义注解和工厂模式动态获取策略实例,完全消除了条件判断语句。
4. 策略模式的高级应用技巧
4.1 使用枚举简化策略选择
对于策略类型固定的场景,可以使用枚举来管理策略:
java复制public enum PromotionTypeEnum {
FULL_REDUCTION {
@Override
public PromotionStrategy getStrategy() {
return new FullReductionStrategy();
}
},
DISCOUNT {
@Override
public PromotionStrategy getStrategy() {
return new DiscountStrategy();
}
};
public abstract PromotionStrategy getStrategy();
}
// 使用方式
PromotionTypeEnum.FULL_REDUCTION.getStrategy().applyPromotion(price);
4.2 Lambda表达式实现轻量级策略
Java 8以后,对于简单的策略逻辑,可以直接使用Lambda表达式:
java复制Map<String, Function<BigDecimal, BigDecimal>> strategies = new HashMap<>();
strategies.put("FULL_REDUCTION", price ->
price.compareTo(THRESHOLD) > 0 ? price.subtract(DISCOUNT) : price);
strategies.put("DISCOUNT", price -> price.multiply(DISCOUNT_RATE));
// 调用示例
Function<BigDecimal, BigDecimal> strategy = strategies.get(type);
BigDecimal finalPrice = strategy.apply(originalPrice);
4.3 策略组合模式
有时我们需要组合多个策略,可以通过装饰器模式实现:
java复制public class CombinedPromotionStrategy implements PromotionStrategy {
private List<PromotionStrategy> strategies;
public CombinedPromotionStrategy(PromotionStrategy... strategies) {
this.strategies = Arrays.asList(strategies);
}
@Override
public BigDecimal applyPromotion(BigDecimal price) {
BigDecimal result = price;
for (PromotionStrategy strategy : strategies) {
result = strategy.applyPromotion(result);
}
return result;
}
}
// 使用示例:先满减再打折
PromotionStrategy strategy = new CombinedPromotionStrategy(
new FullReductionStrategy(),
new DiscountStrategy()
);
5. 实战中的坑与最佳实践
5.1 常见问题排查
-
策略选择错误:确保客户端传入的策略类型与预期一致。可以在工厂类中添加类型校验:
java复制if (!strategyMap.containsKey(type)) { throw new IllegalArgumentException("无效的策略类型: " + type); } -
线程安全问题:如果策略对象有状态,需要确保线程安全。无状态的策略对象是最佳实践。
-
性能考虑:频繁创建策略对象可能影响性能,可以考虑使用对象池或缓存策略实例。
5.2 性能优化技巧
- 策略对象复用:对于无状态的策略对象,可以设计为单例
- 提前加载:在系统启动时预加载所有策略类,避免运行时首次加载的延迟
- 缓存计算结果:对于计算密集型的策略,可以考虑加入缓存机制
5.3 测试策略模式
策略模式的一个优势是便于单元测试。测试时可以单独测试每个策略实现:
java复制public class FullReductionStrategyTest {
@Test
public void testApplyPromotion() {
PromotionStrategy strategy = new FullReductionStrategy();
assertEquals(new BigDecimal("90"),
strategy.applyPromotion(new BigDecimal("100")));
}
}
对于上下文类,可以使用Mock对象测试策略调用:
java复制@Test
public void testContextExecution() {
PromotionStrategy mockStrategy = mock(PromotionStrategy.class);
when(mockStrategy.applyPromotion(any())).thenReturn(new BigDecimal("80"));
PromotionContext context = new PromotionContext(mockStrategy);
BigDecimal result = context.executeStrategy(new BigDecimal("100"));
assertEquals(new BigDecimal("80"), result);
verify(mockStrategy).applyPromotion(new BigDecimal("100"));
}
6. 策略模式在开源项目中的应用
许多知名开源框架都大量使用了策略模式,例如:
- Java Collections.sort():比较器Comparator就是典型的策略接口
- Spring Security:不同的认证策略(表单登录、OAuth2等)都是策略模式的实现
- Log4j2:日志输出策略(控制台、文件、网络等)可以动态切换
以Spring Security为例,查看其认证策略配置:
java复制@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.formLogin() // 表单登录策略
.loginPage("/login")
.permitAll()
.and()
.oauth2Login() // OAuth2登录策略
.loginPage("/login")
.and()
.httpBasic(); // HTTP基本认证策略
}
}
这种设计使得安全策略可以灵活组合,非常便于扩展。
