策略模式实战:从电商促销到算法替换
1. 策略模式:从业务场景到代码落地
第一次接触策略模式是在五年前的一个电商促销系统重构项目。当时系统里有十几套促销规则,从满减、折扣到积分兑换,每种促销的计算逻辑都硬编码在一个巨型Service类里。每次新增促销类型,都要在这个已经超过3000行的类里添加if-else分支。直到某天运营提出"促销规则可动态配置"的需求,我才真正理解了策略模式的价值。
策略模式(Strategy Pattern)属于行为型设计模式,其核心思想是将算法家族分别封装,使它们可以互相替换。这种模式让算法的变化独立于使用算法的客户端——就像给手机安装不同的APP来实现不同功能,而不需要为了新功能去改造手机硬件。
2. 策略模式的三要素解析
2.1 策略接口:定义作战蓝图
所有具体策略的公共契约,通常用接口或抽象类实现。在电商促销案例中,我们的策略接口是这样的:
public interface PromotionStrategy {
BigDecimal applyPromotion(BigDecimal originalPrice);
String getStrategyName();
}
这个简单的接口强制所有具体策略必须实现价格计算方法和策略标识。接口设计的艺术在于:既要足够抽象以覆盖各种实现,又要足够具体以提供明确约束。我建议策略接口的方法控制在3-5个,过多会导致实现负担,过少则可能限制灵活性。
2.2 具体策略类:多样化的战术实现
每个具体策略都是接口的一个独立实现。比如满减策略:
public class FullReductionStrategy implements PromotionStrategy {
private final BigDecimal condition;
private final BigDecimal reduction;
public FullReductionStrategy(BigDecimal condition, BigDecimal reduction) {
this.condition = condition;
this.reduction = reduction;
}
@Override
public BigDecimal applyPromotion(BigDecimal originalPrice) {
return originalPrice.compareTo(condition) >= 0 ?
originalPrice.subtract(reduction) : originalPrice;
}
@Override
public String getStrategyName() {
return "满" + condition + "减" + reduction;
}
}
关键点在于:具体策略应该是无状态的纯算法实现。如果需要配置参数(如满减的阈值和金额),应该通过构造函数注入。这保证了策略实例的线程安全,也是函数式编程思想的体现。
2.3 上下文环境:战场的指挥官
Context类负责维护策略引用,并对外提供统一的服务接口:
public class PromotionContext {
private PromotionStrategy strategy;
public void setStrategy(PromotionStrategy strategy) {
this.strategy = strategy;
}
public BigDecimal executeStrategy(BigDecimal price) {
if(strategy == null) throw new IllegalStateException("未设置促销策略");
return strategy.applyPromotion(price);
}
}
上下文环境的一个高级技巧是提供默认策略。我们在项目中添加了NullObject模式实现:
public class NoPromotionStrategy implements PromotionStrategy {
@Override
public BigDecimal applyPromotion(BigDecimal originalPrice) {
return originalPrice;
}
@Override
public String getStrategyName() { return "无促销"; }
}
这样即使没有设置策略,系统也能优雅降级而不是抛出异常。
3. 策略模式的五种典型应用场景
3.1 业务规则动态切换
在金融领域,不同客户等级对应不同的手续费计算规则。使用策略模式可以运行时切换:
// C#实现示例
public interface ICommissionStrategy {
decimal Calculate(decimal amount);
}
public class VIPCommissionStrategy : ICommissionStrategy {
public decimal Calculate(decimal amount) => amount * 0.01m;
}
public class Context {
private ICommissionStrategy _strategy;
public void SetStrategy(ICommissionStrategy strategy) => _strategy = strategy;
public decimal ExecuteCalculation(decimal amount) => _strategy?.Calculate(amount) ?? 0m;
}
3.2 算法替代与优化
机器学习项目中,特征选择算法可能需要频繁更换。策略模式让算法对比实验变得简单:
# Python实现
from abc import ABC, abstractmethod
class FeatureSelectionStrategy(ABC):
@abstractmethod
def select_features(self, X, y): pass
class RFEStrategy(FeatureSelectionStrategy):
def select_features(self, X, y):
# 递归特征消除实现
pass
class SelectKBestStrategy(FeatureSelectionStrategy):
def select_features(self, X, y):
# 统计检验选择实现
pass
3.3 多平台差异处理
移动端开发中,Android和iOS的推送机制不同:
interface PushNotificationStrategy {
fun sendNotification(message: String)
}
class AndroidPushStrategy : PushNotificationStrategy {
override fun sendNotification(message: String) {
// 调用FCM实现
}
}
class iOSPushStrategy : PushNotificationStrategy {
override fun sendNotification(message: String) {
// 调用APNs实现
}
}
3.4 测试替身(Test Double)
单元测试时可以用Mock策略替代真实实现:
// JavaScript测试示例
class PaymentStrategy {
pay(amount) { throw new Error("抽象方法必须重写"); }
}
class MockPaymentStrategy extends PaymentStrategy {
pay(amount) {
console.log(`模拟支付 ${amount} 元`);
return "mock-transaction-id";
}
}
3.5 游戏AI行为控制
游戏NPC的不同行为模式:
// C++实现
class AIBehaviorStrategy {
public:
virtual ~AIBehaviorStrategy() = default;
virtual void execute() = 0;
};
class AggressiveBehavior : public AIBehaviorStrategy {
void execute() override {
// 攻击性行为逻辑
}
};
class DefensiveBehavior : public AIBehaviorStrategy {
void execute() override {
// 防御性行为逻辑
}
};
4. 策略模式的高级实现技巧
4.1 策略工厂与自动注册
当策略类很多时,可以用工厂模式管理:
public class StrategyFactory {
private static final Map<String, PromotionStrategy> strategies = new HashMap<>();
static {
register("FULL_REDUCTION", new FullReductionStrategy(100, 20));
register("DISCOUNT", new DiscountStrategy(0.8));
}
public static void register(String code, PromotionStrategy strategy) {
strategies.put(code, strategy);
}
public static PromotionStrategy getStrategy(String code) {
return strategies.get(code);
}
}
更优雅的方式是结合Spring等框架的自动发现:
@Component
public class StrategyRegistry {
@Autowired
private Map<String, PromotionStrategy> strategyMap;
public PromotionStrategy getStrategy(String strategyName) {
return strategyMap.get(strategyName + "Strategy");
}
}
4.2 Lambda表达式实现策略
Java 8+可以用函数式接口简化策略模式:
@FunctionalInterface
public interface PromotionStrategy {
BigDecimal apply(BigDecimal price);
static PromotionStrategy fullReduction(BigDecimal condition, BigDecimal reduction) {
return price -> price.compareTo(condition) >= 0 ?
price.subtract(reduction) : price;
}
}
// 使用示例
PromotionStrategy strategy = PromotionStrategy.fullReduction(
new BigDecimal("100"), new BigDecimal("20"));
4.3 策略组合模式
多个策略可以组合使用,比如先打折再满减:
class CompositePromotionStrategy:
def __init__(self, strategies):
self._strategies = strategies
def apply(self, price):
result = price
for strategy in self._strategies:
result = strategy.apply(result)
return result
# 使用示例
strategies = [DiscountStrategy(0.9), FullReductionStrategy(200, 50)]
composite = CompositePromotionStrategy(strategies)
final_price = composite.apply(original_price)
4.4 策略模式与Spring配置
在Spring中可以通过配置动态切换策略:
<bean id="promotionContext" class="com.example.PromotionContext">
<property name="strategy" ref="${current.promotion.strategy}"/>
</bean>
<bean id="discountStrategy" class="com.example.DiscountStrategy">
<constructor-arg value="0.8"/>
</bean>
5. 策略模式的边界与反模式
5.1 何时不该使用策略模式
- 简单条件判断 :如果只有2-3个固定算法,且很少变化,直接if-else可能更简单
- 算法需要共享状态 :策略之间如果需要大量共享数据,可能更适合状态模式
- 客户端需要了解策略细节 :如果使用策略时需要知道太多实现细节,违背了封装原则
5.2 常见实现误区
误区一:策略类膨胀
// 错误示范:策略类承担太多职责
class BadStrategy {
void doA() {...}
void doB() {...}
void doC() {...}
}
修正方法:遵循单一职责原则,拆分为多个细粒度策略。
误区二:上下文过于复杂
class OverEngineeredContext {
private Strategy strategy;
private HistoryManager history;
private Logger logger;
private Validator validator;
// ...
}
修正方法:上下文应该只关注策略的执行,其他职责委托给专门的服务。
误区三:策略创建开销大
// 每次调用都新建策略实例
class WastefulContext {
void execute(String type) {
Strategy strategy = createStrategy(type); // 创建开销大
strategy.execute();
}
}
修正方法:使用享元模式缓存策略实例,或依赖注入单例策略。
6. 策略模式与其他模式的关系
6.1 策略模式 vs 状态模式
相似点:
- 都有上下文和多个实现类
- 都通过委托改变行为
不同点:
- 策略模式:客户端主动选择策略
- 状态模式:状态自动转换,客户端不感知
6.2 策略模式 vs 工厂模式
组合使用示例:
// C#示例
public class StrategyFactory {
public static ISortingStrategy Create(string algorithm) {
return algorithm switch {
"Quick" => new QuickSortStrategy(),
"Merge" => new MergeSortStrategy(),
_ => new BubbleSortStrategy()
};
}
}
public class SortingContext {
public void Sort(int[] data, string algorithm) {
var strategy = StrategyFactory.Create(algorithm);
strategy.Sort(data);
}
}
6.3 策略模式与模板方法模式
模板方法模式在父类定义算法骨架,子类实现某些步骤;策略模式则是完全替换整个算法。可以组合使用:
class AnalysisTemplate:
def analyze(self, data):
self.preprocess(data)
self.run_algorithm(data) # 抽象方法
self.postprocess(results)
class FastAnalysis(AnalysisTemplate):
def run_algorithm(self, data):
return FastStrategy().execute(data)
class AccurateAnalysis(AnalysisTemplate):
def run_algorithm(self, data):
return AccurateStrategy().execute(data)
7. 实战:电商促销系统改造全流程
7.1 改造前代码分析
原始代码问题:
- 单个PromotionService类3000+行
- 新增促销类型需要修改核心类
- 难以进行单元测试
- 无法动态更换算法
典型代码片段:
public BigDecimal calculatePrice(Order order) {
if (promoType.equals("DISCOUNT")) {
return order.getAmount().multiply(discountRate);
} else if (promoType.equals("FULL_REDUCTION")) {
if (order.getAmount().compareTo(condition) > 0) {
return order.getAmount().subtract(reduction);
}
}
// 更多if-else...
}
7.2 重构步骤详解
第一步:定义策略接口
public interface PromotionStrategy {
BigDecimal apply(BigDecimal originalPrice);
boolean supports(String promoType);
}
第二步:实现具体策略
public class DiscountStrategy implements PromotionStrategy {
private final BigDecimal discountRate;
public DiscountStrategy(BigDecimal discountRate) {
this.discountRate = discountRate;
}
@Override
public BigDecimal apply(BigDecimal originalPrice) {
return originalPrice.multiply(discountRate);
}
@Override
public boolean supports(String promoType) {
return "DISCOUNT".equals(promoType);
}
}
第三步:创建策略工厂
public class PromotionStrategyFactory {
private List<PromotionStrategy> strategies;
public PromotionStrategyFactory(List<PromotionStrategy> strategies) {
this.strategies = strategies;
}
public PromotionStrategy getStrategy(String promoType) {
return strategies.stream()
.filter(s -> s.supports(promoType))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("未知促销类型"));
}
}
第四步:改造上下文
@Service
public class PromotionService {
private final PromotionStrategyFactory factory;
public PromotionService(PromotionStrategyFactory factory) {
this.factory = factory;
}
public BigDecimal applyPromotion(Order order, String promoType) {
PromotionStrategy strategy = factory.getStrategy(promoType);
return strategy.apply(order.getAmount());
}
}
7.3 改造效果对比
| 指标 | 改造前 | 改造后 |
|---|---|---|
| 核心类行数 | 3000+ | <500 |
| 新增促销类型 | 修改核心类 | 新增策略类 |
| 单元测试覆盖率 | 35% | 85% |
| 算法切换成本 | 需要重新部署 | 运行时动态切换 |
8. 策略模式的演进与最佳实践
8.1 现代语言中的演进
Java函数式编程风格:
Map<String, Function<BigDecimal, BigDecimal>> strategies = Map.of(
"DISCOUNT", amount -> amount.multiply(0.8),
"FULL_REDUCTION", amount -> amount.compareTo(100) > 0 ?
amount.subtract(20) : amount
);
TypeScript的类型安全实现:
interface Strategy<T, R> {
execute(input: T): R;
}
class Context<T, R> {
constructor(private strategy: Strategy<T, R>) {}
setStrategy(strategy: Strategy<T, R>) {
this.strategy = strategy;
}
execute(input: T): R {
return this.strategy.execute(input);
}
}
8.2 性能优化建议
- 策略缓存 :频繁使用的策略应该缓存实例
- 避免过度抽象 :简单场景不必强行套用模式
- 并行策略 :CPU密集型策略可以考虑并行执行
- 懒加载 :不常用的策略可以延迟初始化
8.3 测试策略模式
有效的测试策略:
- 为每个具体策略编写独立测试
- 测试上下文类与不同策略的交互
- 验证策略替换的正确性
- 边界条件测试(如空策略、无效输入)
示例测试用例:
@Test
void shouldApplyDiscountStrategy() {
PromotionStrategy strategy = new DiscountStrategy(new BigDecimal("0.8"));
BigDecimal result = strategy.apply(new BigDecimal("100"));
assertEquals(new BigDecimal("80"), result);
}
@Test
void shouldThrowWhenNoStrategySet() {
PromotionContext context = new PromotionContext();
assertThrows(IllegalStateException.class, () -> {
context.executeStrategy(new BigDecimal("100"));
});
}
9. 从策略模式看设计原则
9.1 开闭原则(OCP)
策略模式完美体现了"对扩展开放,对修改关闭"的原则。新增算法只需添加新策略类,无需修改现有代码。我在电商项目中,曾经在不停机的情况下通过热部署添加了"第二件半价"的新策略。
9.2 单一职责原则(SRP)
每个策略类只关注一种算法实现。比如折扣策略不关心满减逻辑,满减策略也不涉及折扣计算。这使得每个类的职责清晰明确,修改影响范围可控。
9.3 依赖倒置原则(DIP)
高层模块(上下文)不依赖低层模块(具体策略),二者都依赖抽象。在Spring项目中,我们这样注入依赖:
@Service
public class OrderService {
private final PromotionStrategy strategy; // 依赖接口
@Autowired
public OrderService(@Qualifier("discountStrategy") PromotionStrategy strategy) {
this.strategy = strategy;
}
}
9.4 接口隔离原则(ISP)
策略接口应该保持精简。曾经设计过一个包含10个方法的策略接口,后来发现很多策略类被迫实现不需要的方法。重构后拆分为三个更专注的接口,系统更灵活了。
10. 策略模式的行业应用案例
10.1 支付网关集成
某银行支付系统需要支持多种支付方式:
class PaymentStrategy(ABC):
@abstractmethod
def pay(self, amount): pass
class CreditCardStrategy(PaymentStrategy):
def pay(self, amount):
# 调用信用卡支付API
pass
class PayPalStrategy(PaymentStrategy):
def pay(self, amount):
# 调用PayPal SDK
pass
class PaymentProcessor:
def __init__(self):
self._strategy = None
def set_strategy(self, strategy):
self._strategy = strategy
def execute_payment(self, amount):
if not self._strategy:
raise ValueError("未设置支付策略")
return self._strategy.pay(amount)
10.2 物流运费计算
电商物流系统根据不同快递公司计算运费:
public interface ShippingStrategy {
BigDecimal calculateFee(Order order);
}
public class SFShipping implements ShippingStrategy {
public BigDecimal calculateFee(Order order) {
// 顺丰计价规则
}
}
public class YTShipping implements ShippingStrategy {
public BigDecimal calculateFee(Order order) {
// 圆通计价规则
}
}
10.3 游戏难度系统
动作游戏根据玩家选择调整难度:
class DifficultyStrategy {
public:
virtual ~DifficultyStrategy() = default;
virtual float getEnemyHealth() = 0;
virtual float getEnemyDamage() = 0;
};
class EasyDifficulty : public DifficultyStrategy {
float getEnemyHealth() override { return 50.0f; }
float getEnemyDamage() override { return 5.0f; }
};
class HardDifficulty : public DifficultyStrategy {
float getEnemyHealth() override { return 150.0f; }
float getEnemyDamage() override { return 20.0f; }
};
10.4 数据导出系统
SAAS平台支持多种格式数据导出:
class ExportStrategy {
export(data) {
throw new Error('抽象方法必须实现');
}
}
class CSVExportStrategy extends ExportStrategy {
export(data) {
// 生成CSV逻辑
}
}
class PDFExportStrategy extends ExportStrategy {
export(data) {
// 生成PDF逻辑
}
}
class ExportContext {
constructor() {
this.strategy = null;
}
setStrategy(strategy) {
this.strategy = strategy;
}
executeExport(data) {
if (!this.strategy) throw new Error('未设置导出策略');
return this.strategy.export(data);
}
}
11. 策略模式的局限性与替代方案
11.1 何时考虑其他模式
- 状态模式 :当行为随对象内部状态改变时
- 命令模式 :需要支持撤销/重做操作时
- 模板方法模式 :算法骨架固定只有部分步骤变化时
- 插件架构 :需要更动态的扩展机制时
11.2 策略模式的扩展变体
策略+责任链模式 :
public class ChainOfStrategies implements PromotionStrategy {
private List<PromotionStrategy> strategies;
public ChainOfStrategies(List<PromotionStrategy> strategies) {
this.strategies = strategies;
}
@Override
public BigDecimal apply(BigDecimal originalPrice) {
BigDecimal result = originalPrice;
for (PromotionStrategy strategy : strategies) {
result = strategy.apply(result);
}
return result;
}
}
策略+装饰器模式 :
class PromotionalDecorator:
def __init__(self, base_strategy, decorator_strategy):
self._base = base_strategy
self._decorator = decorator_strategy
def apply(self, price):
return self._decorator.apply(self._base.apply(price))
12. 个人实践中的经验总结
五年间在七个项目中应用策略模式,积累了一些血泪教训:
-
策略命名要直观 :曾经用Strategy1、Strategy2这样的命名,三个月后连自己都分不清。后来采用"业务+算法"的命名方式,如"ShippingCostCalculatorByWeight"。
-
避免策略膨胀 :有个项目策略类增长到2000行,实际上应该拆分为多个子策略。现在我会在策略超过300行时考虑重构。
-
上下文保持精简 :曾犯过在上下文中添加太多辅助方法的错误,导致与策略耦合。现在严格限定上下文只做两件事:持有策略和调用策略。
-
文档比代码更重要 :策略接口必须详细文档说明契约,特别是前置条件和后置条件。我们团队现在要求每个策略接口都有使用示例。
-
性能考量 :在高频交易系统中,发现频繁创建策略实例导致GC压力。解决方案是使用策略对象池,性能提升40%。
最成功的案例是在物流调度系统中,我们用策略模式实现了十多种路径规划算法,并支持运行时切换。这个设计经受住了五年业务变化的考验,新增算法从未影响现有功能。策略模式不是银弹,但当你在多个if-else或switch-case中看到相同的行为模式时,它就是最合适的选择之一。
更多推荐




所有评论(0)