设计模式详解-策略模式

设计模式详解:策略模式

一、模式概述

策略模式(Strategy Pattern)是行为型设计模式中最具灵活性与可扩展性的模式,其核心意图在于定义一系列的算法,把它们一个个封装起来,并且使它们可互相替换。策略模式让算法的变化独立于使用算法的客户,从而避免了冗长的条件分支语句,实现了开闭原则与单一职责原则的优雅统一。

策略模式的命名源自其本质——"策略"即达成目标的方案或方法。在军事领域,面对不同地形、敌情、后勤条件,指挥官选择不同的作战策略;在商业领域,面对不同市场、竞品、用户群体,企业选择不同的定价策略。这些现实隐喻均指向同一核心特征:根据上下文动态选择行为方式,而非将行为硬编码为固定逻辑

策略模式的深层价值在于将算法从使用算法的代码中彻底解耦。在传统的实现中,一个类可能包含巨大的switchif-else块,根据条件选择不同的算法分支。这种设计违反了开闭原则——新增算法需要修改原有类,且不同算法的代码纠缠在一起,难以独立测试与维护。策略模式通过将每个算法封装为独立的策略类,使它们可以独立开发、独立测试、独立部署,甚至运行时动态切换。

二、模式结构

策略模式包含三个核心角色,形成清晰的委托替换结构:

抽象策略(Strategy):定义所有支持的算法的公共接口。Context使用这个接口来调用某ConcreteStrategy定义的算法。

具体策略(Concrete Strategy):以Strategy接口实现某具体算法。

上下文(Context):用一个ConcreteStrategy对象来配置,维护一个对Strategy对象的引用,定义一个接口让Strategy可以访问它的数据。

关键在于上下文持有策略的引用,而非继承策略。这一组合机制使得策略可以运行时替换,策略的增删改不影响上下文代码,上下文的变化也不影响策略实现。

三、深度案例:企业级定价引擎

以下展示一个真实场景下的策略模式应用——电商平台的定价引擎,支持多种定价策略的动态组合与实时切换,涵盖促销、会员、渠道、区域等复杂维度。

3.1 问题域分析:条件分支的噩梦

java
// 反模式:巨型条件分支的定价计算 public class NaivePricingService { public BigDecimal calculatePrice(Product product, Customer customer, Channel channel, LocalDateTime time) { BigDecimal basePrice = product.getBasePrice(); // 促销判断 if (time.isAfter(promotionStart) && time.isBefore(promotionEnd)) { if (promotionType == PromotionType.DIRECT_DISCOUNT) { basePrice = basePrice.multiply(promotionDiscount); } else if (promotionType == PromotionType.FIXED_AMOUNT) { basePrice = basePrice.subtract(promotionAmount); } else if (promotionType == PromotionType.BUY_X_GET_Y) { // 复杂计算... } // 更多促销类型... } // 会员折扣 if (customer != null && customer.isMember()) { if (customer.getLevel() == MemberLevel.GOLD) { basePrice = basePrice.multiply(new BigDecimal("0.85")); } else if (customer.getLevel() == MemberLevel.SILVER) { basePrice = basePrice.multiply(new BigDecimal("0.90")); } // 更多会员等级... } // 渠道折扣 if (channel == Channel.APP) { basePrice = basePrice.multiply(new BigDecimal("0.98")); } else if (channel == Channel.MINI_PROGRAM) { basePrice = basePrice.multiply(new BigDecimal("0.97")); } // 区域定价 if (region == Region.TIER_1_CITY) { basePrice = basePrice.multiply(new BigDecimal("1.05")); } // 更多维度... return basePrice.max(BigDecimal.ZERO); // 防止负数 } }

上述代码每新增一种策略,需修改服务类,测试回归成本高昂,且不同策略的代码相互干扰。策略模式将此重构为可插拔的策略体系。

3.2 抽象策略:定价策略接口

java
/** * 抽象策略:定价策略 * 定义策略的公共接口与上下文访问方式 */ public interface PricingStrategy { /** * 策略标识 */ String getStrategyId(); /** * 策略名称(用于展示) */ String getDisplayName(); /** * 策略优先级(数值越小优先级越高,用于排序) */ int getPriority(); /** * 判断是否适用于当前上下文 */ boolean isApplicable(PricingContext context); /** * 执行定价计算 * @param basePrice 输入价格 * @param context 定价上下文(包含客户、渠道、时间等) * @return 计算结果,包含调整后的价格与调整明细 */ PricingResult apply(BigDecimal basePrice, PricingContext context); /** * 获取策略的元数据(用于监控和分析) */ StrategyMetadata getMetadata(); } /** * 定价上下文:策略执行所需的外部数据 * 上下文对象封装了策略决策所需的全部信息 */ public class PricingContext { private final Product product; private final Customer customer; private final Channel channel; private final Region region; private final LocalDateTime orderTime; private final OrderType orderType; private final List<Coupon> appliedCoupons; private final DeviceInfo deviceInfo; private final String referralCode; // 构建方法... public static Builder builder() { return new Builder(); } } /** * 定价结果:包含调整后价格与调整明细 */ public class PricingResult { private final BigDecimal finalPrice; private final BigDecimal originalPrice; private final List<PriceAdjustment> adjustments; private final List<String> appliedStrategyIds; private final LocalDateTime calculatedAt; // 辅助方法 public BigDecimal getDiscountAmount() { return originalPrice.subtract(finalPrice); } public double getDiscountRate() { return finalPrice.divide(originalPrice, 4, RoundingMode.HALF_UP) .doubleValue(); } }

3.3 具体策略:多样化定价实现

java
/** * 具体策略:直降折扣 */ @Component public class DirectDiscountStrategy implements PricingStrategy { private final PromotionRepository promotionRepository; @Override public String getStrategyId() { return "DIRECT_DISCOUNT"; } @Override public String getDisplayName() { return "限时直降"; } @Override public int getPriority() { return 100; } @Override public boolean isApplicable(PricingContext context) { return promotionRepository.findActiveDirectDiscount( context.getProduct().getId(), context.getOrderTime() ).isPresent(); } @Override public PricingResult apply(BigDecimal basePrice, PricingContext context) { DirectDiscountPromotion promo = promotionRepository .findActiveDirectDiscount(context.getProduct().getId(), context.getOrderTime()) .orElseThrow(() -> new IllegalStateException("策略适用性检查未通过")); BigDecimal discountedPrice = basePrice.multiply(promo.getDiscountRate()); return PricingResult.builder() .finalPrice(discountedPrice.max(promo.getMinPrice())) // 底价保护 .originalPrice(basePrice) .adjustments(List.of(PriceAdjustment.builder() .type(AdjustmentType.DIRECT_DISCOUNT) .strategyId(getStrategyId()) .description(promo.getName()) .amount(basePrice.subtract(discountedPrice)) .metadata(Map.of("discountRate", promo.getDiscountRate().toString())) .build())) .appliedStrategyIds(List.of(getStrategyId())) .calculatedAt(LocalDateTime.now()) .build(); } @Override public StrategyMetadata getMetadata() { return StrategyMetadata.builder() .category(StrategyCategory.PROMOTION) .stackable(false) // 直降通常不可叠加 .exclusiveWith(Set.of("FIXED_AMOUNT", "BUY_X_GET_Y")) .build(); } } /** * 具体策略:会员等级折扣 */ @Component public class MemberLevelDiscountStrategy implements PricingStrategy { private final MemberService memberService; @Override public String getStrategyId() { return "MEMBER_LEVEL"; } @Override public String getDisplayName() { return "会员专享"; } @Override public int getPriority() { return 200; } // 优先级低于促销 @Override public boolean isApplicable(PricingContext context) { Customer customer = context.getCustomer(); return customer != null && customer.isMember() && customer.getMemberLevel() != MemberLevel.NONE; } @Override public PricingResult apply(BigDecimal basePrice, PricingContext context) { MemberLevel level = context.getCustomer().getMemberLevel(); BigDecimal discountRate = level.getDiscountRate(); BigDecimal discountedPrice = basePrice.multiply(discountRate); return PricingResult.builder() .finalPrice(discountedPrice) .originalPrice(basePrice) .adjustments(List.of(PriceAdjustment.builder() .type(AdjustmentType.MEMBER_DISCOUNT) .strategyId(getStrategyId()) .description(level.getDisplayName() + "专享价") .amount(basePrice.subtract(discountedPrice)) .metadata(Map.of("memberLevel", level.name())) .build())) .appliedStrategyIds(List.of(getStrategyId())) .calculatedAt(LocalDateTime.now()) .build(); } @Override public StrategyMetadata getMetadata() { return StrategyMetadata.builder() .category(StrategyCategory.MEMBER) .stackable(true) .exclusiveWith(Set.of()) .build(); } } /** * 具体策略:满减优惠 */ @Component public class ThresholdDiscountStrategy implements PricingStrategy { private final ThresholdPromotionRepository promotionRepository; @Override public String getStrategyId() { return "THRESHOLD_DISCOUNT"; } @Override public String getDisplayName() { return "满减优惠"; } @Override public int getPriority() { return 150; } @Override public boolean isApplicable(PricingContext context) { // 满减基于订单金额,需计算当前小计 BigDecimal subtotal = context.getSubtotal(); return promotionRepository.findApplicableThreshold(subtotal, context.getOrderTime()).isPresent(); } @Override public PricingResult apply(BigDecimal basePrice, PricingContext context) { // 满减策略特殊:基于订单小计而非单品价格 BigDecimal subtotal = context.getSubtotal(); ThresholdPromotion promo = promotionRepository .findApplicableThreshold(subtotal, context.getOrderTime()) .orElseThrow(); // 按比例分摊到各商品 BigDecimal discountRatio = basePrice.divide(subtotal, 10, RoundingMode.HALF_UP); BigDecimal itemDiscount = promo.getDiscountAmount().multiply(discountRatio); return PricingResult.builder() .finalPrice(basePrice.subtract(itemDiscount)) .originalPrice(basePrice) .adjustments(List.of(PriceAdjustment.builder() .type(AdjustmentType.THRESHOLD_DISCOUNT) .strategyId(getStrategyId()) .description("满" + promo.getThreshold() + "减" + promo.getDiscountAmount()) .amount(itemDiscount) .metadata(Map.of("threshold", promo.getThreshold().toString())) .build())) .appliedStrategyIds(List.of(getStrategyId())) .calculatedAt(LocalDateTime.now()) .build(); } @Override public StrategyMetadata getMetadata() { return StrategyMetadata.builder() .category(StrategyCategory.PROMOTION) .stackable(true) .exclusiveWith(Set.of("DIRECT_DISCOUNT")) .build(); } } /** * 具体策略:动态定价(基于AI的实时价格优化) */ @Component public class DynamicPricingStrategy implements PricingStrategy { private final PricingModelService modelService; private final FeatureExtractor featureExtractor; @Override public String getStrategyId() { return "DYNAMIC_AI"; } @Override public String getDisplayName() { return "智能定价"; } @Override public int getPriority() { return 50; } // 最高优先级,覆盖其他策略 @Override public boolean isApplicable(PricingContext context) { // 仅对特定品类、特定时段启用 return context.getProduct().getCategory().isDynamicPricingEnabled() && context.getOrderTime().getHour() >= 20; // 晚间高峰 } @Override public PricingResult apply(BigDecimal basePrice, PricingContext context) { // 提取特征 Map<String, Double> features = featureExtractor.extract(context); // 调用模型预测最优价格 double optimalPrice = modelService.predictOptimalPrice( context.getProduct().getId(), features); // 价格边界保护 BigDecimal minPrice = basePrice.multiply(new BigDecimal("0.7")); BigDecimal maxPrice = basePrice.multiply(new BigDecimal("1.3")); BigDecimal finalPrice = BigDecimal.valueOf(optimalPrice) .min(maxPrice).max(minPrice) .setScale(2, RoundingMode.HALF_UP); return PricingResult.builder() .finalPrice(finalPrice) .originalPrice(basePrice) .adjustments(List.of(PriceAdjustment.builder() .type(AdjustmentType.DYNAMIC_PRICING) .strategyId(getStrategyId()) .description("AI智能定价") .amount(basePrice.subtract(finalPrice)) .metadata(Map.of("modelVersion", modelService.getCurrentVersion())) .build())) .appliedStrategyIds(List.of(getStrategyId())) .calculatedAt(LocalDateTime.now()) .build(); } @Override public StrategyMetadata getMetadata() { return StrategyMetadata.builder() .category(StrategyCategory.AI) .stackable(false) .exclusiveWith(Set.of("DIRECT_DISCOUNT", "THRESHOLD_DISCOUNT", "MEMBER_LEVEL")) .build(); } }

3.4 上下文与策略编排:策略的执行与组合

java
/** * 上下文:定价引擎 * 负责策略的发现、筛选、排序、执行与结果合并 */ @Component public class PricingEngine { private final List<PricingStrategy> allStrategies; private final StrategyRegistry strategyRegistry; private final PricingCache pricingCache; private final MeterRegistry meterRegistry; @Autowired public PricingEngine(List<PricingStrategy> strategies, StrategyRegistry registry, PricingCache cache, MeterRegistry meterRegistry) { // 按优先级排序,确保执行顺序 this.allStrategies = strategies.stream() .sorted(Comparator.comparingInt(PricingStrategy::getPriority)) .collect(Collectors.toList()); this.strategyRegistry = registry; this.pricingCache = cache; this.meterRegistry = meterRegistry; } /** * 执行定价:发现适用策略,按优先级链式应用 */ public PricingResult calculatePrice(PricingContext context) { String cacheKey = generateCacheKey(context); // 尝试缓存 PricingResult cached = pricingCache.get(cacheKey); if (cached != null) { meterRegistry.counter("pricing.cache.hit").increment(); return cached; } long startTime = System.currentTimeMillis(); // 1. 筛选适用策略 List<PricingStrategy> applicableStrategies = allStrategies.stream() .filter(s -> s.isApplicable(context)) .collect(Collectors.toList()); // 2. 检查互斥关系,构建执行计划 ExecutionPlan plan = buildExecutionPlan(applicableStrategies); if (plan.hasConflicts()) { // 记录冲突,按业务规则消解 log.warn("定价策略冲突: {}", plan.getConflicts()); } // 3. 按优先级链式执行 BigDecimal currentPrice = context.getProduct().getBasePrice(); List<PriceAdjustment> allAdjustments = new ArrayList<>(); Set<String> appliedIds = new LinkedHashSet<>(); for (PricingStrategy strategy : plan.getExecutionOrder()) { try { PricingResult stepResult = strategy.apply(currentPrice, context); currentPrice = stepResult.getFinalPrice(); allAdjustments.addAll(stepResult.getAdjustments()); appliedIds.addAll(stepResult.getAppliedStrategyIds()); // 记录策略应用 meterRegistry.counter("pricing.strategy.applied", "strategy", strategy.getStrategyId()).increment(); } catch (Exception e) { // 单个策略失败不影响整体,记录后继续 meterRegistry.counter("pricing.strategy.failed", "strategy", strategy.getStrategyId()).increment(); log.error("策略执行失败: {}", strategy.getStrategyId(), e); } } // 4. 最终保护:价格边界 currentPrice = applyPriceGuards(currentPrice, context); // 5. 组装结果 PricingResult result = PricingResult.builder() .finalPrice(currentPrice) .originalPrice(context.getProduct().getBasePrice()) .adjustments(allAdjustments) .appliedStrategyIds(new ArrayList<>(appliedIds)) .calculatedAt(LocalDateTime.now()) .build(); // 6. 缓存结果 pricingCache.put(cacheKey, result, getCacheTtl(context)); meterRegistry.timer("pricing.calculate").record( System.currentTimeMillis() - startTime, TimeUnit.MILLISECONDS); return result; } /** * 构建执行计划:处理策略互斥与依赖 */ private ExecutionPlan buildExecutionPlan(List<PricingStrategy> strategies) { ExecutionPlan plan = new ExecutionPlan(); // 检测互斥 for (int i = 0; i < strategies.size(); i++) { for (int j = i + 1; j < strategies.size(); j++) { PricingStrategy a = strategies.get(i); PricingStrategy b = strategies.get(j); if (isExclusive(a, b)) { // 优先级高的保留 if (a.getPriority() < b.getPriority()) { plan.exclude(b, "与" + a.getStrategyId() + "互斥"); } else { plan.exclude(a, "与" + b.getStrategyId() + "互斥"); } } } } // 按优先级排序最终执行序列 plan.setExecutionOrder(strategies.stream() .filter(s -> !plan.isExcluded(s)) .sorted(Comparator.comparingInt(PricingStrategy::getPriority)) .collect(Collectors.toList())); return plan; } private boolean isExclusive(PricingStrategy a, PricingStrategy b) { Set<String> exclusiveA = a.getMetadata().getExclusiveWith(); Set<String> exclusiveB = b.getMetadata().getExclusiveWith(); return exclusiveA.contains(b.getStrategyId()) || exclusiveB.contains(a.getStrategyId()); } private BigDecimal applyPriceGuards(BigDecimal price, PricingContext context) { // 最低价格保护:不低于成本 BigDecimal minPrice = context.getProduct().getCostPrice() .multiply(new BigDecimal("1.05")); // 5%毛利保护 // 最高价格保护:不高于市场价200% BigDecimal maxPrice = context.getProduct().getMarketPrice() .multiply(new BigDecimal("2.0")); return price.max(minPrice).min(maxPrice); } private String generateCacheKey(PricingContext context) { // 基于影响定价的关键维度生成缓存键 return String.format("price:%s:%s:%s:%s:%s", context.getProduct().getId(), context.getCustomer() != null ? context.getCustomer().getId() : "anon", context.getChannel(), context.getRegion(), context.getOrderTime().format(DateTimeFormatter.ofPattern("yyyyMMddHH"))); } private Duration getCacheTtl(PricingContext context) { // 动态定价缓存时间短,固定价格缓存时间长 boolean hasDynamic = context.getAppliedStrategies().stream() .anyMatch(s -> s.getMetadata().getCategory() == StrategyCategory.AI); return hasDynamic ? Duration.ofMinutes(5) : Duration.ofHours(1); } }

3.5 策略的动态注册与热切换

java
/** * 策略注册中心:支持运行时动态注册、卸载、更新策略 */ @Component public class StrategyRegistry { private final ConcurrentHashMap<String, PricingStrategy> strategyMap = new ConcurrentHashMap<>(); private final ApplicationEventPublisher eventPublisher; /** * 注册新策略(热部署支持) */ public void register(PricingStrategy strategy) { PricingStrategy old = strategyMap.put(strategy.getStrategyId(), strategy); eventPublisher.publishEvent(new StrategyChangedEvent( strategy.getStrategyId(), old == null ? ChangeType.ADDED : ChangeType.UPDATED, strategy.getMetadata() )); } /** * 卸载策略 */ public void unregister(String strategyId) { PricingStrategy removed = strategyMap.remove(strategyId); if (removed != null) { eventPublisher.publishEvent(new StrategyChangedEvent( strategyId, ChangeType.REMOVED, null)); } } /** * 基于配置动态加载策略(如从数据库或配置中心) */ @Scheduled(fixedDelay = 60000) // 每分钟检查更新 public void refreshFromConfig() { List<StrategyConfig> configs = loadStrategyConfigs(); for (StrategyConfig config : configs) { if (config.isEnabled() && !strategyMap.containsKey(config.getId())) { // 动态加载策略类 PricingStrategy strategy = instantiateStrategy(config); register(strategy); } else if (!config.isEnabled() && strategyMap.containsKey(config.getId())) { unregister(config.getId()); } } } /** * A/B测试:为不同用户群体返回不同策略实现 */ public PricingStrategy resolveStrategy(String strategyId, Customer customer) { PricingStrategy base = strategyMap.get(strategyId); if (base == null) return null; // 检查是否有实验版本 Experiment experiment = experimentService.getActiveExperiment(strategyId); if (experiment == null) return base; String variant = experiment.assignVariant(customer.getId()); if ("control".equals(variant)) return base; // 返回实验变体 return strategyMap.getOrDefault(strategyId + "_exp_" + variant, base); } }

四、策略模式的高级主题

4.1 函数式策略:Lambda与策略模式

Java 8+中,函数式接口可简化策略实现:

java
/** * 函数式策略接口 */ @FunctionalInterface public interface PricingStrategy { PricingResult apply(BigDecimal basePrice, PricingContext context); // 默认方法提供策略元数据 default String getStrategyId() { return "anonymous"; } default boolean isApplicable(PricingContext context) { return true; } } // 使用Lambda内联定义策略 PricingStrategy flashSale = (price, ctx) -> { if (ctx.getOrderTime().getHour() == 20) { return PricingResult.of(price.multiply(new BigDecimal("0.5"))); } return PricingResult.of(price); }; // 注册到引擎 engine.register("FLASH_SALE", flashSale);

4.2 策略与责任链的融合

java
/** * 策略链:多个策略顺序执行,支持提前终止 */ public class StrategyChain implements PricingStrategy { private final List<PricingStrategy> chain; private final TerminationCondition termination; @Override public PricingResult apply(BigDecimal basePrice, PricingContext context) { BigDecimal currentPrice = basePrice; for (PricingStrategy strategy : chain) { if (!strategy.isApplicable(context)) continue; PricingResult result = strategy.apply(currentPrice, context); currentPrice = result.getFinalPrice(); if (termination.shouldTerminate(result)) { break; } } return PricingResult.of(currentPrice); } } // 终止条件:价格已低于阈值 TerminationCondition stopOnLowPrice = result -> result.getFinalPrice().compareTo(new BigDecimal("10")) < 0;

五、策略模式与相关模式的辨析

策略 vs 状态模式:策略模式由客户端选择策略,状态模式由状态机自动转换状态。策略模式是"多选一",状态模式是"自动流转"。

策略 vs 模板方法:策略模式封装完整算法,通过组合替换;模板方法定义算法骨架,通过继承覆盖步骤。策略模式是"整体替换",模板方法是"局部替换"。

策略 vs 命令模式:策略封装算法,命令封装操作请求。策略关注"怎么做",命令关注"做什么"。

六、设计陷阱与规避策略

陷阱一:策略数量爆炸

过多策略导致管理困难。解决方案:策略分类、策略组合(复合策略)、或策略工厂动态生成。

陷阱二:上下文对象膨胀

上下文传递大量无关数据。解决方案:按需构建上下文,或使用ThreadLocal传递隐式上下文。

陷阱三:策略选择逻辑复杂

选择策略的条件本身成为复杂逻辑。解决方案:将选择逻辑也策略化(策略选择器模式),或使用规则引擎。

七、结语

策略模式是应对行为变化的核心设计工具,它将算法的选择与算法的实现解耦,使系统能够灵活应对业务规则的频繁变更。在定价、路由、排序、验证、压缩等算法密集的领域,策略模式是架构设计的基石。理解其接口契约、掌握策略发现与编排机制、善用函数式简化、警惕策略膨胀与选择复杂化,是运用好这一模式的关键。策略模式的精髓在于承认变化是常态,将变化封装为可管理、可替换、可演化的策略单元,从而在不确定的业务环境中构建确定的软件结构。

返回知识中心