1. 策略模式概述
策略模式(Strategy Pattern)是一种行为型设计模式,它定义了一系列算法,并将每个算法封装起来,使它们可以相互替换。这种模式让算法的变化独立于使用算法的客户端。
在实际开发中,我们经常会遇到需要根据不同条件执行不同算法的场景。比如电商平台的促销活动(满减、折扣、赠品)、支付方式选择(支付宝、微信、银行卡)等。传统做法可能会使用大量的if-else或switch-case语句,而策略模式提供了一种更优雅的解决方案。
2. 策略模式的核心结构
2.1 模式组成
策略模式通常包含三个核心角色:
- Context(环境类):持有一个策略类的引用,最终给客户端调用
- Strategy(抽象策略类):定义所有支持的算法的公共接口
- ConcreteStrategy(具体策略类):实现了抽象策略定义的接口,提供具体的算法实现
2.2 UML类图
java复制+----------------+ +------------------+
| Context | | Strategy |
|----------------| |------------------|
| +strategy:Strategy |<>----| +algorithm():void |
| +contextInterface()| +------------------+
+----------------+ ^
|
+----------------+----------------+
| |
+----------------+ +----------------+
| ConcreteStrategyA | | ConcreteStrategyB |
|----------------| |----------------|
| +algorithm() | | +algorithm() |
+----------------+ +----------------+
3. 策略模式实践案例
3.1 电商促销场景实现
假设我们有一个电商系统,需要实现不同的促销策略:
java复制// 抽象策略接口
public interface PromotionStrategy {
BigDecimal applyPromotion(BigDecimal price);
}
// 具体策略类:满减
public class FullReductionStrategy implements PromotionStrategy {
@Override
public BigDecimal applyPromotion(BigDecimal price) {
if (price.compareTo(new BigDecimal("200")) >= 0) {
return price.subtract(new BigDecimal("50"));
}
return price;
}
}
// 具体策略类:折扣
public class DiscountStrategy implements PromotionStrategy {
@Override
public BigDecimal applyPromotion(BigDecimal price) {
return price.multiply(new BigDecimal("0.8"));
}
}
// 环境类
public class PromotionContext {
private PromotionStrategy strategy;
public PromotionContext(PromotionStrategy strategy) {
this.strategy = strategy;
}
public BigDecimal executeStrategy(BigDecimal price) {
return strategy.applyPromotion(price);
}
// 可选的策略切换方法
public void setStrategy(PromotionStrategy strategy) {
this.strategy = strategy;
}
}
// 客户端使用
public class Client {
public static void main(String[] args) {
PromotionContext context = new PromotionContext(new FullReductionStrategy());
BigDecimal result = context.executeStrategy(new BigDecimal("300"));
System.out.println("满减策略结果:" + result);
context.setStrategy(new DiscountStrategy());
result = context.executeStrategy(new BigDecimal("300"));
System.out.println("折扣策略结果:" + result);
}
}
3.2 支付方式选择实现
另一个典型应用是支付方式的选择:
java复制// 支付策略接口
public interface PaymentStrategy {
void pay(BigDecimal amount);
}
// 支付宝支付
public class AlipayStrategy implements PaymentStrategy {
@Override
public void pay(BigDecimal amount) {
System.out.println("使用支付宝支付:" + amount);
// 调用支付宝SDK实现
}
}
// 微信支付
public class WechatPayStrategy implements PaymentStrategy {
@Override
public void pay(BigDecimal amount) {
System.out.println("使用微信支付:" + amount);
// 调用微信支付SDK实现
}
}
// 支付上下文
public class PaymentContext {
private PaymentStrategy strategy;
public PaymentContext(PaymentStrategy strategy) {
this.strategy = strategy;
}
public void executePayment(BigDecimal amount) {
strategy.pay(amount);
}
}
// 使用示例
public class PaymentDemo {
public static void main(String[] args) {
PaymentContext context = new PaymentContext(new AlipayStrategy());
context.executePayment(new BigDecimal("100.00"));
context = new PaymentContext(new WechatPayStrategy());
context.executePayment(new BigDecimal("200.00"));
}
}
4. 策略模式的进阶应用
4.1 结合工厂模式
在实际项目中,我们常常将策略模式与工厂模式结合使用,以简化客户端的调用:
java复制public class StrategyFactory {
private static final Map<String, PromotionStrategy> STRATEGY_MAP = new HashMap<>();
static {
STRATEGY_MAP.put("FULL_REDUCTION", new FullReductionStrategy());
STRATEGY_MAP.put("DISCOUNT", new DiscountStrategy());
// 可以添加更多策略
}
public static PromotionStrategy getStrategy(String type) {
if (!STRATEGY_MAP.containsKey(type)) {
throw new IllegalArgumentException("未知的策略类型:" + type);
}
return STRATEGY_MAP.get(type);
}
}
// 使用方式
PromotionStrategy strategy = StrategyFactory.getStrategy("FULL_REDUCTION");
PromotionContext context = new PromotionContext(strategy);
BigDecimal result = context.executeStrategy(price);
4.2 使用枚举简化策略模式
对于策略类型固定的场景,可以使用枚举来实现策略模式:
java复制public enum PromotionEnumStrategy {
FULL_REDUCTION {
@Override
public BigDecimal apply(BigDecimal price) {
return price.compareTo(new BigDecimal("200")) >= 0 ?
price.subtract(new BigDecimal("50")) : price;
}
},
DISCOUNT {
@Override
public BigDecimal apply(BigDecimal price) {
return price.multiply(new BigDecimal("0.8"));
}
};
public abstract BigDecimal apply(BigDecimal price);
}
// 使用方式
BigDecimal result = PromotionEnumStrategy.FULL_REDUCTION.apply(price);
4.3 Spring中的策略模式
在Spring框架中,我们可以利用依赖注入来管理策略:
java复制// 定义策略接口
public interface ShippingStrategy {
BigDecimal calculateFee(Order order);
}
// 具体策略实现
@Component("standardShipping")
public class StandardShippingStrategy implements ShippingStrategy {
@Override
public BigDecimal calculateFee(Order order) {
// 标准运费计算逻辑
}
}
@Component("expressShipping")
public class ExpressShippingStrategy implements ShippingStrategy {
@Override
public BigDecimal calculateFee(Order order) {
// 快递运费计算逻辑
}
}
// 使用策略的服务
@Service
public class ShippingService {
private final Map<String, ShippingStrategy> strategyMap;
@Autowired
public ShippingService(List<ShippingStrategy> strategies) {
strategyMap = strategies.stream()
.collect(Collectors.toMap(
s -> s.getClass().getAnnotation(Component.class).value(),
Function.identity()
));
}
public BigDecimal calculateShippingFee(String strategyType, Order order) {
ShippingStrategy strategy = strategyMap.get(strategyType);
if (strategy == null) {
throw new IllegalArgumentException("未知的运费策略:" + strategyType);
}
return strategy.calculateFee(order);
}
}
5. 策略模式的优缺点与适用场景
5.1 优点
- 避免多重条件语句:策略模式可以避免使用大量的条件语句,使代码更清晰
- 开闭原则:易于扩展新的策略,无需修改现有代码
- 算法复用:可以在系统的不同部分复用相同的策略
- 单元测试友好:每个策略都可以独立测试
5.2 缺点
- 客户端必须了解所有策略:客户端需要知道有哪些策略以及它们的区别
- 策略类数量增加:每个策略都需要一个单独的类,可能增加类的数量
- 通信开销:策略与上下文之间可能需要交换数据,增加通信开销
5.3 适用场景
- 一个系统需要在多种算法中选择一种
- 需要封装算法相关的数据
- 一个类定义了多种行为,并且这些行为以多个条件语句的形式出现
- 系统需要动态地在几种算法中选择一种
6. 策略模式的最佳实践
6.1 策略选择与注册
在实际项目中,策略的选择和注册可以更加灵活:
java复制// 策略注册表
public class StrategyRegistry {
private final Map<String, Supplier<PromotionStrategy>> registry = new HashMap<>();
public void register(String name, Supplier<PromotionStrategy> supplier) {
registry.put(name, supplier);
}
public PromotionStrategy getStrategy(String name) {
Supplier<PromotionStrategy> supplier = registry.get(name);
if (supplier == null) {
throw new IllegalArgumentException("未知策略:" + name);
}
return supplier.get();
}
}
// 初始化注册表
StrategyRegistry registry = new StrategyRegistry();
registry.register("FULL_REDUCTION", FullReductionStrategy::new);
registry.register("DISCOUNT", DiscountStrategy::new);
// 使用
PromotionStrategy strategy = registry.getStrategy("FULL_REDUCTION");
6.2 策略与Lambda表达式
在Java 8+中,可以使用函数式接口和Lambda表达式简化策略模式:
java复制// 定义函数式接口
@FunctionalInterface
public interface PromotionStrategy {
BigDecimal apply(BigDecimal price);
}
// 策略定义
Map<String, PromotionStrategy> strategies = new HashMap<>();
strategies.put("FULL_REDUCTION", price ->
price.compareTo(new BigDecimal("200")) >= 0 ?
price.subtract(new BigDecimal("50")) : price);
strategies.put("DISCOUNT", price -> price.multiply(new BigDecimal("0.8")));
// 使用
BigDecimal result = strategies.get("FULL_REDUCTION").apply(price);
6.3 策略模式与状态模式的区别
虽然策略模式和状态模式在结构上相似,但它们的意图不同:
- 策略模式:客户端主动选择策略,策略之间通常互不影响
- 状态模式:状态转换由上下文或状态自身控制,客户端不直接指定状态
7. 常见问题与解决方案
7.1 策略选择逻辑复杂
问题:当策略选择逻辑变得复杂时,可能会引入新的条件语句。
解决方案:可以使用责任链模式或规则引擎来处理策略选择。
java复制public interface StrategySelector {
Optional<PromotionStrategy> select(Order order);
}
public class DefaultStrategySelector implements StrategySelector {
private List<StrategySelectionRule> rules;
@Override
public Optional<PromotionStrategy> select(Order order) {
for (StrategySelectionRule rule : rules) {
Optional<PromotionStrategy> strategy = rule.apply(order);
if (strategy.isPresent()) {
return strategy;
}
}
return Optional.empty();
}
}
@FunctionalInterface
public interface StrategySelectionRule {
Optional<PromotionStrategy> apply(Order order);
}
7.2 策略需要共享数据
问题:多个策略可能需要访问相同的数据,导致上下文类变得臃肿。
解决方案:将共享数据封装成独立的参数对象。
java复制public class PromotionContext {
private PromotionStrategy strategy;
private PromotionData data;
public PromotionContext(PromotionStrategy strategy, PromotionData data) {
this.strategy = strategy;
this.data = data;
}
public BigDecimal executeStrategy() {
return strategy.applyPromotion(data);
}
}
public class PromotionData {
private BigDecimal price;
private User user;
private Product product;
// getters and setters
}
public interface PromotionStrategy {
BigDecimal applyPromotion(PromotionData data);
}
7.3 策略的动态加载
问题:需要在不重启应用的情况下添加新策略。
解决方案:使用类加载机制或脚本引擎实现动态策略。
java复制public class DynamicStrategyLoader {
private final StrategyRegistry registry;
private final String strategyDirectory;
public void loadStrategies() throws Exception {
// 扫描目录中的JAR文件
Files.list(Paths.get(strategyDirectory))
.filter(path -> path.toString().endsWith(".jar"))
.forEach(this::loadStrategy);
}
private void loadStrategy(Path jarPath) {
try (URLClassLoader loader = new URLClassLoader(
new URL[]{jarPath.toUri().toURL()},
getClass().getClassLoader())) {
// 假设每个JAR包含META-INF/strategy.properties
URL config = loader.getResource("META-INF/strategy.properties");
Properties props = new Properties();
props.load(config.openStream());
String name = props.getProperty("strategy.name");
String className = props.getProperty("strategy.class");
@SuppressWarnings("unchecked")
Class<PromotionStrategy> clazz = (Class<PromotionStrategy>) loader.loadClass(className);
PromotionStrategy strategy = clazz.getDeclaredConstructor().newInstance();
registry.register(name, () -> strategy);
} catch (Exception e) {
// 处理异常
}
}
}
8. 实际项目中的策略模式应用
8.1 电商平台中的定价策略
在电商平台中,商品价格可能受到多种因素影响:
java复制public interface PricingStrategy {
BigDecimal calculatePrice(Product product, User user, int quantity);
}
// 会员折扣策略
public class MemberDiscountStrategy implements PricingStrategy {
@Override
public BigDecimal calculatePrice(Product product, User user, int quantity) {
BigDecimal basePrice = product.getPrice().multiply(new BigDecimal(quantity));
if (user.isPremiumMember()) {
return basePrice.multiply(new BigDecimal("0.9"));
} else if (user.isRegularMember()) {
return basePrice.multiply(new BigDecimal("0.95"));
}
return basePrice;
}
}
// 批量购买折扣策略
public class BulkPurchaseStrategy implements PricingStrategy {
@Override
public BigDecimal calculatePrice(Product product, User user, int quantity) {
if (quantity >= 10) {
return product.getPrice()
.multiply(new BigDecimal(quantity))
.multiply(new BigDecimal("0.85"));
} else if (quantity >= 5) {
return product.getPrice()
.multiply(new BigDecimal(quantity))
.multiply(new BigDecimal("0.9"));
}
return product.getPrice().multiply(new BigDecimal(quantity));
}
}
// 组合策略
public class CompositePricingStrategy implements PricingStrategy {
private final List<PricingStrategy> strategies;
public CompositePricingStrategy(List<PricingStrategy> strategies) {
this.strategies = strategies;
}
@Override
public BigDecimal calculatePrice(Product product, User user, int quantity) {
BigDecimal price = product.getPrice().multiply(new BigDecimal(quantity));
for (PricingStrategy strategy : strategies) {
price = strategy.calculatePrice(product, user, quantity);
}
return price;
}
}
8.2 游戏开发中的AI行为策略
在游戏开发中,策略模式常用于实现不同的AI行为:
java复制public interface AIStrategy {
void execute(GameCharacter character, GameWorld world);
}
// 攻击型策略
public class AggressiveStrategy implements AIStrategy {
@Override
public void execute(GameCharacter character, GameWorld world) {
// 寻找最近的敌人
GameCharacter enemy = findNearestEnemy(character, world);
if (enemy != null) {
// 移动到敌人附近
moveToward(character, enemy.getPosition());
// 如果足够近就攻击
if (distance(character.getPosition(), enemy.getPosition()) < 2) {
attack(character, enemy);
}
}
}
// 辅助方法...
}
// 防御型策略
public class DefensiveStrategy implements AIStrategy {
@Override
public void execute(GameCharacter character, GameWorld world) {
// 检查生命值
if (character.getHealth() < 0.3 * character.getMaxHealth()) {
// 寻找最近的庇护所
Position safePlace = findNearestShelter(character, world);
if (safePlace != null) {
moveToward(character, safePlace);
}
} else {
// 适度参与战斗
GameCharacter enemy = findWeakestEnemy(character, world);
if (enemy != null) {
// 保持距离攻击
if (distance(character.getPosition(), enemy.getPosition()) < 5) {
rangedAttack(character, enemy);
}
}
}
}
// 辅助方法...
}
// AI控制器
public class AIController {
private AIStrategy currentStrategy;
public void update(GameCharacter character, GameWorld world) {
// 根据情况选择策略
if (character.getHealth() < 0.3 * character.getMaxHealth()) {
currentStrategy = new DefensiveStrategy();
} else if (world.getEnemiesNearby(character).size() > 3) {
currentStrategy = new AggressiveStrategy();
} else {
currentStrategy = new PatrolStrategy();
}
// 执行当前策略
currentStrategy.execute(character, world);
}
}
8.3 金融系统中的风险评估策略
在金融系统中,不同的客户可能需要不同的风险评估策略:
java复制public interface RiskAssessmentStrategy {
RiskLevel assess(Client client, FinancialProduct product);
}
// 保守型风险评估
public class ConservativeRiskAssessment implements RiskAssessmentStrategy {
@Override
public RiskLevel assess(Client client, FinancialProduct product) {
if (client.getAge() > 60 || client.getNetWorth() < product.getMinInvestment()) {
return RiskLevel.HIGH;
}
// 其他保守评估逻辑...
return RiskLevel.MEDIUM;
}
}
// 进取型风险评估
public class AggressiveRiskAssessment implements RiskAssessmentStrategy {
@Override
public RiskLevel assess(Client client, FinancialProduct product) {
if (client.getRiskTolerance() == RiskTolerance.HIGH &&
client.getInvestmentExperience() > 5) {
return RiskLevel.LOW;
}
// 其他进取评估逻辑...
return RiskLevel.MEDIUM;
}
}
// 风险评估服务
public class RiskAssessmentService {
private final Map<ClientType, RiskAssessmentStrategy> strategies;
public RiskAssessmentService() {
strategies = new EnumMap<>(ClientType.class);
strategies.put(ClientType.CONSERVATIVE, new ConservativeRiskAssessment());
strategies.put(ClientType.AGGRESSIVE, new AggressiveRiskAssessment());
strategies.put(ClientType.BALANCED, new BalancedRiskAssessment());
}
public RiskLevel assessRisk(Client client, FinancialProduct product) {
RiskAssessmentStrategy strategy = strategies.get(client.getType());
if (strategy == null) {
strategy = new DefaultRiskAssessment();
}
return strategy.assess(client, product);
}
}
