设计模式详解-策略模式
设计模式详解:策略模式
一、模式概述
策略模式(Strategy Pattern)是行为型设计模式中最具灵活性与可扩展性的模式,其核心意图在于定义一系列的算法,把它们一个个封装起来,并且使它们可互相替换。策略模式让算法的变化独立于使用算法的客户,从而避免了冗长的条件分支语句,实现了开闭原则与单一职责原则的优雅统一。
策略模式的命名源自其本质——"策略"即达成目标的方案或方法。在军事领域,面对不同地形、敌情、后勤条件,指挥官选择不同的作战策略;在商业领域,面对不同市场、竞品、用户群体,企业选择不同的定价策略。这些现实隐喻均指向同一核心特征:根据上下文动态选择行为方式,而非将行为硬编码为固定逻辑。
策略模式的深层价值在于将算法从使用算法的代码中彻底解耦。在传统的实现中,一个类可能包含巨大的switch或if-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传递隐式上下文。
陷阱三:策略选择逻辑复杂
选择策略的条件本身成为复杂逻辑。解决方案:将选择逻辑也策略化(策略选择器模式),或使用规则引擎。
七、结语
策略模式是应对行为变化的核心设计工具,它将算法的选择与算法的实现解耦,使系统能够灵活应对业务规则的频繁变更。在定价、路由、排序、验证、压缩等算法密集的领域,策略模式是架构设计的基石。理解其接口契约、掌握策略发现与编排机制、善用函数式简化、警惕策略膨胀与选择复杂化,是运用好这一模式的关键。策略模式的精髓在于承认变化是常态,将变化封装为可管理、可替换、可演化的策略单元,从而在不确定的业务环境中构建确定的软件结构。