策略模式实战:从电商优惠到高并发优化
1. 策略模式初探:为什么我们需要它?
第一次接触策略模式时,我正面临一个典型的业务场景:电商平台的优惠计算系统。当时系统里充斥着各种if-else判断,双十一要打折、会员日要满减、新用户要首单优惠...每次新增活动类型,都像是在已经摇摇欲坠的代码堆上再摞一块砖。直到某天凌晨三点,当我第N次修改calculateDiscount()方法时,突然意识到——这不该是面向对象编程该有的样子。
策略模式的核心思想其实很简单:定义一系列算法,将它们封装成独立的类,并使它们可以相互替换。这种模式让算法的变化独立于使用它的客户端。听起来很抽象?想象你是个餐厅老板,收银台就是客户端,而各种支付方式(现金、信用卡、移动支付)就是不同的策略。无论顾客选择哪种支付方式,收银台的工作流程都不需要改变。
关键理解:策略模式不是用来解决"有没有"的问题,而是解决"多选一"的问题。当你的系统中存在多种相似但又有差异的算法实现时,就该考虑策略模式了。
2. 策略模式的经典实现
2.1 UML类图解析
标准的策略模式包含三个角色:
- Context(环境类):持有一个Strategy的引用,负责与客户端交互
- Strategy(抽象策略):定义算法接口
- ConcreteStrategy(具体策略):实现具体算法
用Java代码表示最基础的实现:
// 抽象策略接口
interface DiscountStrategy {
double applyDiscount(double originalPrice);
}
// 具体策略类
class MemberDiscount implements DiscountStrategy {
@Override
public double applyDiscount(double price) {
return price * 0.9; // 会员9折
}
}
class NewUserDiscount implements DiscountStrategy {
@Override
public double applyDiscount(double price) {
return price * 0.8; // 新用户8折
}
}
// 环境类
class ShoppingCart {
private DiscountStrategy strategy;
public void setStrategy(DiscountStrategy strategy) {
this.strategy = strategy;
}
public double checkout(double total) {
return strategy.applyDiscount(total);
}
}
2.2 实际应用中的变体
在实际开发中,我们经常会根据需求调整经典实现。比如在Spring框架中,结合依赖注入可以这样用:
@Service
public class DiscountService {
private Map<String, DiscountStrategy> strategies;
// 通过构造器自动注入所有策略实现
public DiscountService(List<DiscountStrategy> strategyList) {
this.strategies = strategyList.stream()
.collect(Collectors.toMap(
s -> s.getClass().getSimpleName(),
Function.identity()
));
}
public double applyDiscount(String strategyName, double price) {
return strategies.get(strategyName).applyDiscount(price);
}
}
这种变体利用了Spring的自动装配特性,将所有实现策略的Bean收集到Map中,使用时通过名称调用,避免了显式的setter方法。
3. 策略模式在真实项目中的实战
3.1 电商促销系统改造案例
我曾参与过一个年GMV超10亿的电商平台重构,原有的促销系统是这样的:
public BigDecimal calculateDiscount(User user, Order order) {
if (user.isNewUser()) {
return order.getTotal().multiply(BIG_DECIMAL_0_8);
} else if (user.isVIP()) {
return order.getTotal().multiply(BIG_DECIMAL_0_7);
} else if (order.getCreateTime().isAfter(activityStartTime)) {
return order.getTotal().subtract(BigDecimal.TEN);
}
// 更多if-else...
}
改造后的策略模式实现:
// 定义策略接口
public interface PromotionStrategy {
boolean isApplicable(User user, Order order);
BigDecimal applyDiscount(Order order);
}
// 具体策略实现
@Component
public class NewUserStrategy implements PromotionStrategy {
@Override
public boolean isApplicable(User user, Order order) {
return user.isNewUser();
}
@Override
public BigDecimal applyDiscount(Order order) {
return order.getTotal().multiply(BIG_DECIMAL_0_8);
}
}
// 策略上下文
@Service
public class PromotionContext {
@Autowired
private List<PromotionStrategy> strategies;
public BigDecimal execute(Order order) {
return strategies.stream()
.filter(s -> s.isApplicable(order.getUser(), order))
.findFirst()
.map(s -> s.applyDiscount(order))
.orElse(order.getTotal());
}
}
改造后带来的直接收益:
- 新增促销类型只需添加新的Strategy实现类
- 单元测试可以针对每个策略单独测试
- 促销优先级调整只需修改filter逻辑
- 代码行数减少40%,可读性大幅提升
3.2 性能优化:策略对象的复用
在初期实现中,我们每次调用都创建新的策略对象,后来通过对象池优化:
public class StrategyPool {
private static final Map<Class<?>, Object> pool = new ConcurrentHashMap<>();
@SuppressWarnings("unchecked")
public static <T> T getStrategy(Class<T> strategyClass) {
return (T)pool.computeIfAbsent(strategyClass, clazz -> {
try {
return clazz.getDeclaredConstructor().newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}
}
对于无状态的策略对象(即不包含成员变量),可以放心复用实例,减少GC压力。实测在高并发场景下,QPS提升了约15%。
4. 策略模式的高级应用与陷阱
4.1 结合工厂模式动态选择策略
在更复杂的场景中,我们可能需要根据运行时条件动态选择策略。这时可以结合工厂模式:
public class StrategyFactory {
public static DiscountStrategy createStrategy(Order order) {
if (order.getItems().size() > 5) {
return new BulkDiscountStrategy();
} else if (order.getTotal().compareTo(BigDecimal.valueOf(1000)) > 0) {
return new HighValueDiscountStrategy();
}
return new DefaultDiscountStrategy();
}
}
这种组合模式特别适合策略选择逻辑较复杂的场景,但要注意避免工厂方法变得过于庞大。
4.2 常见陷阱与规避方案
陷阱1:策略膨胀 当策略类过多时(超过20个),维护会变得困难。解决方案:
- 按业务维度分组,使用组合策略
- 考虑改用规则引擎
陷阱2:上下文信息传递 策略可能需要访问上下文数据,两种处理方式:
// 方式1:通过方法参数传递
interface Strategy {
void execute(ContextData data);
}
// 方式2:策略持有上下文引用
abstract class AbstractStrategy {
protected Context context;
public AbstractStrategy(Context context) {
this.context = context;
}
}
陷阱3:线程安全问题 如果策略有状态,需要特别注意:
// 错误示例:有状态的策略
class CounterStrategy implements Strategy {
private int count; // 非线程安全
public void execute() {
count++;
}
}
// 正确做法:使用ThreadLocal或每次新建实例
class SafeCounterStrategy implements Strategy {
private final ThreadLocal<Integer> counter = ThreadLocal.withInitial(() -> 0);
public void execute() {
counter.set(counter.get() + 1);
}
}
4.3 策略模式与其他模式的对比
与状态模式的异同:
- 相似:都通过委托改变行为
- 区别:策略模式是主动选择,状态模式是被动转换
与模板方法模式的关系:
- 模板方法在父类定义骨架,子类实现细节
- 策略模式则是完全替换整个算法
在实际项目中,我经常发现新手会混淆这些模式。判断标准很简单:如果变化的是完整算法,用策略模式;如果只是算法中的某些步骤变化,用模板方法更合适。
5. 现代编程语言中的策略模式演进
5.1 Java 8+的函数式实现
自从Java 8引入lambda后,策略模式可以更简洁地实现:
public class DiscountCalculator {
private final Map<String, Function<Double, Double>> strategies = Map.of(
"VIP", price -> price * 0.7,
"NEW", price -> price * 0.8,
"DEFAULT", price -> price
);
public double calculate(String type, double price) {
return strategies.getOrDefault(type, strategies.get("DEFAULT"))
.apply(price);
}
}
5.2 Spring中的策略模式最佳实践
在Spring生态中,策略模式常与这些特性结合:
- 使用
@Conditional实现条件化Bean注册 - 通过
ApplicationContext.getBeansOfType()获取所有策略实现 - 结合
@Primary注解处理默认策略
示例代码:
public interface PaymentStrategy {
void pay(BigDecimal amount);
}
@Service
@ConditionalOnProperty(name = "payment.alipay.enabled", havingValue = "true")
class AlipayStrategy implements PaymentStrategy {
public void pay(BigDecimal amount) {
// 支付宝支付实现
}
}
@Service
@Primary
class DefaultPaymentStrategy implements PaymentStrategy {
public void pay(BigDecimal amount) {
// 默认支付方式
}
}
5.3 策略模式在微服务架构中的应用
在微服务场景下,策略模式演化为:
- 通过Feign Client实现不同服务调用策略
- 使用Spring Cloud LoadBalancer定义路由策略
- 结合配置中心实现动态策略切换
典型架构:
客户端 → API网关 → [策略路由] → 服务A / 服务B / 服务C
↓
策略配置中心
这种架构下,策略的切换可以通过配置中心实时生效,无需重启服务。
更多推荐



所有评论(0)