1. 策略模式初探:为什么我们需要它?
我刚入行Java开发时,最头疼的就是遇到需要频繁修改的业务逻辑。比如电商平台的折扣计算,不同用户等级、不同促销活动都要走不同算法。每次新增一个促销类型,就得在代码里加一堆if-else,最后代码臃肿得像个俄罗斯套娃。
策略模式(Strategy Pattern)就是专门治这个病的良药。它把算法家族分别封装起来,让它们可以互相替换。这么说可能有点抽象,举个生活中的例子:你手机里的导航APP,开车模式、步行模式、公交模式就是不同的策略,切换时不需要改导航核心代码,只需要换个策略就行。
新手常见误区:很多初学者会把策略模式简单理解为"用接口实现多态"。实际上它的精髓在于运行时动态替换算法,以及避免条件分支的滥用。
2. 策略模式三大件:完整类图解析
2.1 核心角色拆解
标准的策略模式包含三个关键角色(以电商折扣为例):
-
Context(上下文):比如订单结算类
- 持有策略接口引用
- 提供策略切换方法
- 示例代码:
java复制public class OrderService { private DiscountStrategy strategy; public void setStrategy(DiscountStrategy strategy) { this.strategy = strategy; } public BigDecimal calculate(Order order) { return strategy.calculate(order); } }
-
Strategy(策略接口):比如折扣计算接口
- 定义算法族通用方法
- 示例代码:
java复制public interface DiscountStrategy { BigDecimal calculate(Order order); }
-
ConcreteStrategy(具体策略):比如VIP折扣、满减折扣等
- 实现具体算法
- 示例代码:
java复制public class VipDiscount implements DiscountStrategy { @Override public BigDecimal calculate(Order order) { return order.getAmount().multiply(new BigDecimal("0.8")); } }
2.2 UML类图实战
用PlantUML绘制的类图更直观:
plantuml复制@startuml
class OrderService {
- strategy: DiscountStrategy
+ setStrategy()
+ calculate()
}
interface DiscountStrategy {
+ calculate()
}
class VipDiscount {
+ calculate()
}
class FullReductionDiscount {
+ calculate()
}
OrderService o-> DiscountStrategy
DiscountStrategy <|-- VipDiscount
DiscountStrategy <|-- FullReductionDiscount
@enduml
3. 电商折扣实战:从if-else到策略模式
3.1 改造前代码(典型反面教材)
新手常写的"面条式"代码:
java复制public BigDecimal calculateDiscount(Order order, String userType) {
if ("VIP".equals(userType)) {
return order.getAmount().multiply(new BigDecimal("0.8"));
} else if ("SVIP".equals(userType)) {
return order.getAmount().multiply(new BigDecimal("0.7"));
} else if (order.getAmount().compareTo(new BigDecimal("100")) > 0) {
return order.getAmount().subtract(new BigDecimal("20"));
}
// 更多if-else...
}
3.2 策略模式改造步骤
-
定义策略接口:
java复制public interface DiscountStrategy { boolean support(User user, Order order); BigDecimal calculate(Order order); } -
实现具体策略:
java复制@Component public class VipDiscountStrategy implements DiscountStrategy { @Override public boolean support(User user, Order order) { return user.getType() == UserType.VIP; } @Override public BigDecimal calculate(Order order) { return order.getAmount().multiply(new BigDecimal("0.8")); } } -
创建策略工厂:
java复制@Component public class DiscountStrategyFactory { @Autowired private List<DiscountStrategy> strategies; public DiscountStrategy getStrategy(User user, Order order) { return strategies.stream() .filter(s -> s.support(user, order)) .findFirst() .orElseThrow(() -> new IllegalArgumentException("无匹配折扣策略")); } } -
上下文调用:
java复制@Service public class OrderService { @Autowired private DiscountStrategyFactory factory; public BigDecimal calculate(Order order, User user) { DiscountStrategy strategy = factory.getStrategy(user, order); return strategy.calculate(order); } }
性能优化技巧:如果策略判断逻辑复杂,可以考虑使用策略缓存(ConcurrentHashMap)来避免每次全量遍历策略列表。
4. Spring环境下的高级玩法
4.1 自动注册策略模式
利用Spring的自动注入特性更优雅地实现:
java复制// 定义策略标记注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface DiscountType {
UserType value();
}
// 策略实现类标注
@DiscountType(UserType.VIP)
public class VipDiscountStrategy implements DiscountStrategy {
// 实现代码...
}
// 工厂类改造
public class DiscountStrategyFactory {
private Map<UserType, DiscountStrategy> strategyMap;
@Autowired
public void initStrategies(List<DiscountStrategy> strategies) {
strategyMap = strategies.stream()
.filter(s -> s.getClass().isAnnotationPresent(DiscountType.class))
.collect(Collectors.toMap(
s -> s.getClass().getAnnotation(DiscountType.class).value(),
Function.identity()
));
}
public DiscountStrategy getStrategy(UserType userType) {
return strategyMap.get(userType);
}
}
4.2 组合策略模式
有时需要组合多个策略(如先满减再VIP折扣):
java复制public class CompositeDiscountStrategy implements DiscountStrategy {
private final List<DiscountStrategy> strategies;
public CompositeDiscountStrategy(DiscountStrategy... strategies) {
this.strategies = Arrays.asList(strategies);
}
@Override
public BigDecimal calculate(Order order) {
BigDecimal result = order.getAmount();
for (DiscountStrategy strategy : strategies) {
result = strategy.calculate(new Order(result));
}
return result;
}
}
// 使用示例
DiscountStrategy strategy = new CompositeDiscountStrategy(
new FullReductionStrategy(),
new VipDiscountStrategy()
);
5. 避坑指南与性能优化
5.1 常见问题排查
-
策略不生效:
- 检查Spring组件扫描路径是否包含策略类
- 确认策略的support()方法逻辑是否正确
- 调试工厂类的策略发现过程
-
策略冲突:
- 多个策略同时满足条件时,可以通过@Order注解指定优先级
- 或者修改support()方法确保互斥
-
NPE问题:
- 在工厂类中添加空策略兜底
- 使用Optional避免空指针
5.2 性能优化方案
-
策略缓存:
java复制public class CachedStrategyFactory { private final Map<StrategyKey, DiscountStrategy> cache = new ConcurrentHashMap<>(); public DiscountStrategy getStrategy(User user, Order order) { StrategyKey key = new StrategyKey(user.getType(), order.getAmount()); return cache.computeIfAbsent(key, k -> strategies.stream() .filter(s -> s.support(user, order)) .findFirst() .orElse(DefaultStrategy.INSTANCE)); } } -
并行计算:
java复制public class ParallelStrategyEvaluator { public Optional<DiscountStrategy> findFirstMatch(User user, Order order) { return strategies.parallelStream() .filter(s -> s.support(user, order)) .findFirst(); } } -
预编译策略:
java复制@PostConstruct public void precompileStrategies() { strategies.forEach(s -> { if (s instanceof Compilable) { ((Compilable) s).compile(); } }); }
6. 策略模式变体与扩展
6.1 带状态的策略
当策略需要维护状态时(如限流计数器):
java复制public class RateLimitStrategy implements DiscountStrategy {
private final RateLimiter limiter;
public RateLimitStrategy(int permitsPerSecond) {
this.limiter = RateLimiter.create(permitsPerSecond);
}
@Override
public BigDecimal calculate(Order order) {
if (!limiter.tryAcquire()) {
throw new RateLimitExceededException();
}
// 实际计算逻辑...
}
}
6.2 动态脚本策略
使用Groovy实现热更新策略:
java复制public class ScriptStrategy implements DiscountStrategy {
private ScriptEngine engine;
private String script;
public void refresh(String newScript) {
this.script = newScript;
this.engine = new GroovyScriptEngine().createScript(script);
}
@Override
public BigDecimal calculate(Order order) {
return (BigDecimal) engine.eval("amount * discountRate",
Map.of("amount", order.getAmount(), "discountRate", 0.8));
}
}
6.3 策略模式+责任链
结合责任链实现复杂业务流:
java复制public abstract class DiscountHandler {
private DiscountHandler next;
public DiscountHandler linkWith(DiscountHandler next) {
this.next = next;
return next;
}
public abstract boolean canHandle(Order order);
public BigDecimal handle(Order order) {
if (canHandle(order)) {
return calculate(order);
}
return next != null ? next.handle(order) : order.getAmount();
}
protected abstract BigDecimal calculate(Order order);
}
7. 测试策略模式的正确姿势
7.1 单元测试示例
java复制public class DiscountStrategyTest {
@Test
void testVipDiscount() {
DiscountStrategy strategy = new VipDiscountStrategy();
Order order = new Order(new BigDecimal("100"));
User user = new User(UserType.VIP);
assertTrue(strategy.support(user, order));
assertEquals(new BigDecimal("80"), strategy.calculate(order));
}
@Test
void testStrategyFactory() {
List<DiscountStrategy> strategies = List.of(
new VipDiscountStrategy(),
new FullReductionStrategy()
);
DiscountStrategyFactory factory = new DiscountStrategyFactory(strategies);
User vipUser = new User(UserType.VIP);
Order order = new Order(new BigDecimal("100"));
assertEquals(VipDiscountStrategy.class,
factory.getStrategy(vipUser, order).getClass());
}
}
7.2 集成测试技巧
- 使用@SpringBootTest测试完整流程
- 用@TestConfiguration模拟策略实现
- 验证策略切换是否影响上下文状态
- 性能测试多策略并发场景
java复制@SpringBootTest
public class OrderServiceIntegrationTest {
@Autowired
private OrderService orderService;
@TestConfiguration
static class TestConfig {
@Bean
public DiscountStrategy testStrategy() {
return new DiscountStrategy() {
@Override
public boolean support(User user, Order order) {
return true;
}
@Override
public BigDecimal calculate(Order order) {
return BigDecimal.TEN;
}
};
}
}
@Test
void testOrderCalculate() {
Order order = new Order(new BigDecimal("100"));
User user = new User(UserType.NORMAL);
assertEquals(BigDecimal.TEN, orderService.calculate(order, user));
}
}
8. 策略模式在开源项目中的经典实现
8.1 JDK中的策略模式
-
Comparator接口:
java复制Collections.sort(list, (a, b) -> b.compareTo(a)); // 降序策略 -
ThreadPoolExecutor的拒绝策略:
java复制new ThreadPoolExecutor(..., new ThreadPoolExecutor.AbortPolicy());
8.2 Spring中的策略应用
-
ResourceLoader:
java复制Resource resource = new DefaultResourceLoader() .getResource("classpath:application.yml"); -
HandlerMapping:
- BeanNameUrlHandlerMapping
- RequestMappingHandlerMapping
8.3 MyBatis的Executor
java复制public interface Executor {
ResultHandler NO_RESULT_HANDLER = null;
int update(MappedStatement ms, Object parameter);
<E> List<E> query(/*参数省略*/);
// 其他策略方法...
}
// 具体实现类
public class SimpleExecutor implements Executor { /*...*/ }
public class BatchExecutor implements Executor { /*...*/ }
9. 策略模式与其他模式的关系
9.1 与工厂模式的区别
| 维度 | 策略模式 | 工厂模式 |
|---|---|---|
| 目的 | 算法互换 | 对象创建 |
| 关注点 | 行为 | 实例化 |
| 运行时 | 可动态切换 | 通常初始化时确定 |
| 典型应用 | 折扣计算 | 数据库连接创建 |
9.2 与状态模式的对比
虽然结构相似,但本质不同:
- 状态模式:状态改变触发行为变化(自动)
- 策略模式:主动切换算法实现不同行为
9.3 与模板方法模式组合
模板方法定义骨架,策略模式填充具体实现:
java复制public abstract class PaymentTemplate {
// 模板方法
public final void process() {
validate();
executePayment();
notifyUser();
}
protected abstract void executePayment(); // 由策略实现
// 其他通用方法...
}
public class AlipayStrategy extends PaymentTemplate {
@Override
protected void executePayment() {
// 支付宝支付实现
}
}
10. 实际项目经验分享
在电商平台重构中,我们用策略模式处理了复杂的促销体系,效果显著:
-
改造前:
- 2000+行的PromotionService类
- 新增促销类型需要修改核心逻辑
- 测试覆盖率不足50%
-
改造后:
- 拆分为15个独立策略类
- 新增促销只需实现新策略
- 核心代码行数减少70%
- 测试覆盖率提升至85%
遇到的坑:
- 策略之间共享状态导致线程安全问题
- 策略发现机制在Spring代理场景失效
- 策略缓存未及时更新导致业务异常
解决方案:
- 使用ThreadLocal隔离策略状态
- 调整AOP代理顺序
- 引入策略版本号机制
性能数据对比(QPS):
| 场景 | 改造前 | 改造后 |
|---|---|---|
| 单一策略 | 1,200 | 1,180 |
| 多策略轮询 | 800 | 1,150 |
| 峰值并发 | 1,500 | 2,300 |
关键收获:策略模式虽然会引入少量性能开销(多一层调用),但带来的可维护性和扩展性提升远大于代价。特别是在频繁变更的业务场景,收益非常明显。