Appearance
策略模式 (Strategy)
一、定义
一句话概括:定义一系列算法,把它们一个个封装起来,并且使它们可以互相替换。策略模式让算法独立于使用它的客户端而变化。
官方定义:Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.
二、解决的问题
2.1 问题场景
在系统中,同一个问题可能有多种解决方式(算法),需要在运行时根据情况选择不同的算法。例如:
- 支付方式:支付宝、微信、银行卡、Apple Pay
- 排序算法:快速排序、归并排序、冒泡排序(根据数据量选择)
- 优惠策略:满减、打折、赠品、积分抵扣
- 压缩算法:ZIP、GZIP、RAR、7z
- 路由策略:轮询、随机、加权、一致性哈希
2.2 不用策略模式会怎样?
java
// 反例:大量 if-else 选择算法
class PaymentService {
public void pay(String type, BigDecimal amount) {
if ("ALIPAY".equals(type)) {
System.out.println("支付宝支付: " + amount);
} else if ("WECHAT".equals(type)) {
System.out.println("微信支付: " + amount);
} else if ("BANK_CARD".equals(type)) {
System.out.println("银行卡支付: " + amount);
} else if ("APPLE_PAY".equals(type)) {
System.out.println("Apple Pay: " + amount);
}
// 每增加一种支付方式,都要修改此方法
// 违反开闭原则
}
}问题:
- 新增算法需要修改原有代码,违反开闭原则
- if-else 分支过多,代码可读性差
- 算法与客户端耦合,无法独立测试
- 无法在运行时动态切换算法
三、结构
3.1 角色组成
| 角色 | 说明 |
|---|---|
| Strategy(抽象策略) | 定义所有策略的公共接口 |
| ConcreteStrategy(具体策略) | 实现 Strategy 接口,提供具体算法 |
| Context(上下文) | 持有 Strategy 引用,调用策略方法 |
| Client(客户端) | 创建具体策略对象并设置到 Context 中 |
3.2 类图(ASCII)
┌──────────────┐ ┌──────────────┐
│ Context │────────►│ Strategy │
├──────────────┤ ├──────────────┤
│ - strategy │ │ + execute() │
│ + setStrategy│ └──────┬───────┘
│ + execute() │ │
└──────────────┘ ┌──────┴──────┐
│ │
▼ ▼
┌──────────┐ ┌──────────┐
│Concrete │ │Concrete │
│StrategyA │ │StrategyB │
└──────────┘ └──────────┘四、代码实现
4.1 基础实现
java
// ==================== 抽象策略:支付接口 ====================
interface PaymentStrategy {
void pay(BigDecimal amount);
String getName();
}
// ==================== 具体策略:支付宝 ====================
class AlipayStrategy implements PaymentStrategy {
@Override
public void pay(BigDecimal amount) {
System.out.println("使用支付宝支付: ¥" + amount);
}
@Override
public String getName() {
return "支付宝";
}
}
// ==================== 具体策略:微信支付 ====================
class WechatPayStrategy implements PaymentStrategy {
@Override
public void pay(BigDecimal amount) {
System.out.println("使用微信支付: ¥" + amount);
}
@Override
public String getName() {
return "微信支付";
}
}
// ==================== 具体策略:银行卡 ====================
class BankCardStrategy implements PaymentStrategy {
private String cardNumber;
public BankCardStrategy(String cardNumber) {
this.cardNumber = cardNumber;
}
@Override
public void pay(BigDecimal amount) {
System.out.println("使用银行卡(" + cardNumber + ")支付: ¥" + amount);
}
@Override
public String getName() {
return "银行卡";
}
}
// ==================== 上下文 ====================
class PaymentContext {
private PaymentStrategy strategy;
public PaymentContext(PaymentStrategy strategy) {
this.strategy = strategy;
}
public void setStrategy(PaymentStrategy strategy) {
this.strategy = strategy;
}
public void executePayment(BigDecimal amount) {
strategy.pay(amount);
}
}
// ==================== 客户端 ====================
public class StrategyDemo {
public static void main(String[] args) {
PaymentContext context = new PaymentContext(new AlipayStrategy());
context.executePayment(new BigDecimal("100.00"));
// 运行时切换策略
context.setStrategy(new WechatPayStrategy());
context.executePayment(new BigDecimal("200.00"));
context.setStrategy(new BankCardStrategy("6222-****-1234"));
context.executePayment(new BigDecimal("300.00"));
}
}4.2 进阶实现
4.2.1 策略枚举
java
// 使用枚举实现策略模式(策略数量固定时适用)
enum DiscountStrategy {
NORMAL("普通") {
@Override
public BigDecimal calculate(BigDecimal price) {
return price;
}
},
VIP("VIP会员") {
@Override
public BigDecimal calculate(BigDecimal price) {
return price.multiply(new BigDecimal("0.85"));
}
},
SUPER_VIP("超级VIP") {
@Override
public BigDecimal calculate(BigDecimal price) {
return price.multiply(new BigDecimal("0.70"));
}
},
NEW_USER("新用户") {
@Override
public BigDecimal calculate(BigDecimal price) {
return price.multiply(new BigDecimal("0.50"));
}
};
private final String description;
DiscountStrategy(String description) {
this.description = description;
}
public String getDescription() { return description; }
public abstract BigDecimal calculate(BigDecimal price);
}
// 使用
public class EnumStrategyDemo {
public static void main(String[] args) {
BigDecimal price = new BigDecimal("1000");
System.out.println("普通价格: " + DiscountStrategy.NORMAL.calculate(price));
System.out.println("VIP价格: " + DiscountStrategy.VIP.calculate(price));
System.out.println("超级VIP价格: " + DiscountStrategy.SUPER_VIP.calculate(price));
System.out.println("新用户价格: " + DiscountStrategy.NEW_USER.calculate(price));
}
}4.2.2 工厂 + 策略模式
java
// 策略工厂
class PaymentStrategyFactory {
private static final Map<String, PaymentStrategy> STRATEGY_MAP = new HashMap<>();
static {
STRATEGY_MAP.put("ALIPAY", new AlipayStrategy());
STRATEGY_MAP.put("WECHAT", new WechatPayStrategy());
STRATEGY_MAP.put("BANK_CARD", new BankCardStrategy(""));
}
public static PaymentStrategy getStrategy(String type) {
PaymentStrategy strategy = STRATEGY_MAP.get(type);
if (strategy == null) {
throw new IllegalArgumentException("不支持的支付方式: " + type);
}
return strategy;
}
// 注册新策略(运行时扩展)
public static void registerStrategy(String type, PaymentStrategy strategy) {
STRATEGY_MAP.put(type, strategy);
}
}
// 使用工厂 + 策略
public class FactoryStrategyDemo {
public static void main(String[] args) {
String payType = "ALIPAY";
PaymentStrategy strategy = PaymentStrategyFactory.getStrategy(payType);
strategy.pay(new BigDecimal("500"));
}
}4.2.3 Spring 注入策略集合
java
// 场景:根据订单类型选择不同的处理策略
// ==================== 策略接口 ====================
interface OrderProcessStrategy {
String getType(); // 策略标识
void process(Order order); // 处理逻辑
}
// ==================== 具体策略 ====================
@Component
class NormalOrderStrategy implements OrderProcessStrategy {
@Override
public String getType() { return "NORMAL"; }
@Override
public void process(Order order) {
System.out.println("处理普通订单: " + order.getId());
}
}
@Component
class VipOrderStrategy implements OrderProcessStrategy {
@Override
public String getType() { return "VIP"; }
@Override
public void process(Order order) {
System.out.println("VIP订单优先处理: " + order.getId());
// VIP 专属逻辑:优先发货、赠送礼品等
}
}
@Component
class PreSaleOrderStrategy implements OrderProcessStrategy {
@Override
public String getType() { return "PRE_SALE"; }
@Override
public void process(Order order) {
System.out.println("预售订单处理: " + order.getId());
// 预售逻辑:锁定库存、延迟发货
}
}
// ==================== 策略上下文(Spring 自动注入所有策略) ====================
@Service
class OrderProcessService {
// 注入所有实现了 OrderProcessStrategy 的 Bean
private final Map<String, OrderProcessStrategy> strategyMap;
@Autowired
public OrderProcessService(List<OrderProcessStrategy> strategies) {
this.strategyMap = strategies.stream()
.collect(Collectors.toMap(
OrderProcessStrategy::getType,
Function.identity()
));
}
public void processOrder(Order order) {
OrderProcessStrategy strategy = strategyMap.get(order.getType());
if (strategy == null) {
throw new IllegalArgumentException("不支持的订单类型: " + order.getType());
}
strategy.process(order);
}
}4.3 生产级实现
Spring Boot 多策略优惠计算
java
// ==================== 优惠上下文 ====================
@Data
class CouponContext {
private Long userId;
private BigDecimal originalPrice;
private List<Coupon> availableCoupons;
private String userLevel; // NORMAL, VIP, SUPER_VIP
}
// ==================== 优惠券实体 ====================
@Data
@AllArgsConstructor
class Coupon {
private String type; // FULL_REDUCTION, DISCOUNT, CASH
private BigDecimal threshold; // 满减门槛
private BigDecimal value; // 满减金额 / 折扣率 / 现金券金额
}
// ==================== 策略接口 ====================
interface CouponStrategy {
String getType();
BigDecimal calculate(CouponContext context, Coupon coupon);
int getPriority(); // 优先级,数值越小优先级越高
}
// ==================== 满减策略 ====================
@Component
class FullReductionStrategy implements CouponStrategy {
@Override
public String getType() { return "FULL_REDUCTION"; }
@Override
public BigDecimal calculate(CouponContext context, Coupon coupon) {
if (context.getOriginalPrice().compareTo(coupon.getThreshold()) >= 0) {
return coupon.getValue();
}
return BigDecimal.ZERO;
}
@Override
public int getPriority() { return 1; }
}
// ==================== 折扣策略 ====================
@Component
class DiscountStrategy implements CouponStrategy {
@Override
public String getType() { return "DISCOUNT"; }
@Override
public BigDecimal calculate(CouponContext context, Coupon coupon) {
// 折扣率在 value 中,如 0.85 表示 85 折
return context.getOriginalPrice().multiply(
BigDecimal.ONE.subtract(coupon.getValue()));
}
@Override
public int getPriority() { return 2; }
}
// ==================== 现金券策略 ====================
@Component
class CashCouponStrategy implements CouponStrategy {
@Override
public String getType() { return "CASH"; }
@Override
public BigDecimal calculate(CouponContext context, Coupon coupon) {
return coupon.getValue();
}
@Override
public int getPriority() { return 3; }
}
// ==================== 优惠计算引擎 ====================
@Service
class CouponEngine {
private final Map<String, CouponStrategy> strategyMap;
private final List<CouponStrategy> sortedStrategies;
@Autowired
public CouponEngine(List<CouponStrategy> strategies) {
this.strategyMap = strategies.stream()
.collect(Collectors.toMap(CouponStrategy::getType, Function.identity()));
this.sortedStrategies = strategies.stream()
.sorted(Comparator.comparingInt(CouponStrategy::getPriority))
.collect(Collectors.toList());
}
/**
* 计算最优优惠组合
*/
public BigDecimal calculateBestDiscount(CouponContext context) {
BigDecimal maxDiscount = BigDecimal.ZERO;
for (Coupon coupon : context.getAvailableCoupons()) {
CouponStrategy strategy = strategyMap.get(coupon.getType());
if (strategy != null) {
BigDecimal discount = strategy.calculate(context, coupon);
if (discount.compareTo(maxDiscount) > 0) {
maxDiscount = discount;
}
}
}
return maxDiscount;
}
/**
* 计算最终价格
*/
public BigDecimal calculateFinalPrice(CouponContext context) {
BigDecimal discount = calculateBestDiscount(context);
return context.getOriginalPrice().subtract(discount);
}
}
// ==================== Controller ====================
@RestController
@RequestMapping("/api/coupon")
class CouponController {
@Autowired private CouponEngine couponEngine;
@PostMapping("/calculate")
public Map<String, Object> calculate(@RequestBody CouponContext context) {
BigDecimal finalPrice = couponEngine.calculateFinalPrice(context);
BigDecimal discount = context.getOriginalPrice().subtract(finalPrice);
return Map.of(
"originalPrice", context.getOriginalPrice(),
"discount", discount,
"finalPrice", finalPrice
);
}
}
// ==================== 测试 ====================
@SpringBootTest
class CouponEngineTest {
@Autowired private CouponEngine couponEngine;
@Test
void testCalculate() {
CouponContext context = new CouponContext();
context.setOriginalPrice(new BigDecimal("1000"));
context.setAvailableCoupons(List.of(
new Coupon("FULL_REDUCTION", new BigDecimal("300"), new BigDecimal("50")),
new Coupon("DISCOUNT", BigDecimal.ZERO, new BigDecimal("0.15")),
new Coupon("CASH", BigDecimal.ZERO, new BigDecimal("30"))
));
BigDecimal finalPrice = couponEngine.calculateFinalPrice(context);
System.out.println("最终价格: " + finalPrice);
// 满减 50, 折扣 150, 现金 30 → 最优是折扣 150,最终 850
}
}策略模式 + 责任链组合
java
// 策略 + 责任链:先选择策略,再执行责任链
interface ValidationStrategy {
boolean validate(Order order);
String getErrorMessage();
}
// 策略链:多个策略按顺序执行
class ValidationChain {
private final List<ValidationStrategy> strategies = new ArrayList<>();
public ValidationChain addStrategy(ValidationStrategy strategy) {
strategies.add(strategy);
return this;
}
public List<String> validate(Order order) {
List<String> errors = new ArrayList<>();
for (ValidationStrategy strategy : strategies) {
if (!strategy.validate(order)) {
errors.add(strategy.getErrorMessage());
}
}
return errors;
}
}
// 使用
@Service
class OrderValidationService {
public List<String> validateOrder(Order order) {
return new ValidationChain()
.addStrategy(new StockValidationStrategy())
.addStrategy(new PriceValidationStrategy())
.addStrategy(new UserValidationStrategy())
.validate(order);
}
}五、优缺点
优点
- 符合开闭原则:新增策略无需修改现有代码,只需新增一个策略类
- 消除 if-else:用多态替代条件判断,代码更清晰
- 算法可复用:策略可以在不同 Context 中共享
- 方便测试:每个策略可以独立进行单元测试
- 运行时切换:可以在运行时动态切换算法
缺点
- 类数量增加:每个策略都需要一个类,Strategy 过多时类膨胀
- 客户端需要了解策略:客户端需要知道有哪些策略并选择合适的策略
- 策略之间不通信:策略之间无法共享数据(除非通过 Context)
- 过度设计风险:对于只有少量算法的情况,策略模式可能过度设计
六、适用场景
- 多种算法/策略选择:支付方式、优惠计算、排序算法
- 消除 if-else:大量条件分支可以根据不同策略划分
- 算法需要动态切换:运行时根据条件选择不同算法
- 算法需要独立测试:每个策略独立开发和测试
- 规则引擎:根据不同的规则执行不同的逻辑
- 数据导出:Excel、PDF、CSV 不同导出格式
七、JDK / Spring 框架中的实际应用
| 框架 | 应用位置 | 说明 |
|---|---|---|
| JDK | java.util.Comparator | 排序策略接口 |
| JDK | java.util.concurrent.ThreadPoolExecutor | 拒绝策略 RejectedExecutionHandler |
| JDK | java.nio.file.FileVisitor | 文件访问策略 |
| Spring | Resource 接口 | 不同资源加载策略 |
| Spring | InstantiationStrategy | Bean 实例化策略 |
| Spring Security | PasswordEncoder | 密码加密策略 |
| Spring Security | AuthenticationProvider | 认证策略 |
| Spring MVC | ViewResolver | 视图解析策略 |
| MyBatis | ExecutorType | SQL 执行策略(SIMPLE, REUSE, BATCH) |
八、与其他模式的关系
与状态模式(重要对比)
- 结构相似:类图几乎一样
- 意图不同:策略模式关注"算法可替换"(客户端选择),状态模式关注"状态变化导致行为变化"(自行切换)
- 生命周期:策略互不感知,状态之间相互感知可切换
- 典型场景:策略 → 支付方式选择;状态 → 订单状态流转
与工厂模式
- 策略模式常与工厂模式结合使用,工厂负责创建策略对象,策略模式负责执行算法
与模板方法模式
- 策略模式通过组合实现算法替换,模板方法模式通过继承实现算法骨架
- 策略模式更灵活(运行时切换),模板方法模式更简单(编译时确定)
与命令模式
- 策略模式关注"如何做"(算法),命令模式关注"做什么"(请求封装)
- 命令模式支持撤销,策略模式不支持
与享元模式
- 无状态的策略对象可以使用享元模式共享,减少内存开销
九、面试常见问题
Q1:策略模式和状态模式有什么区别?
A:最核心的区别在于意图。策略模式中,策略之间互不感知,由客户端主动选择策略,关注的是"算法的可替换性";状态模式中,状态之间可以相互切换(如 CreatedState 在 pay() 后自行切换到 PaidState),关注的是"状态变化导致行为变化"。从代码上看,策略模式中 Context 持有 Strategy 引用,策略之间不持有对方引用;状态模式中,State 可能持有到其他 State 的引用以进行状态切换。
Q2:如何避免策略模式的类膨胀问题?
A:(1) 使用策略枚举:当策略数量固定且逻辑简单时,用枚举实现策略;(2) 使用 Lambda 表达式(Java 8+):对于函数式接口的策略,可以直接用 Lambda 替代;\n(3) 组合策略:将多个小策略合并为一个策略类;(4) 使用配置文件:通过配置文件定义策略,避免硬编码。
Q3:Spring 中如何优雅地注入多个策略实现?
A:使用 List<Strategy> 注入所有实现了 Strategy 接口的 Bean,然后通过 Map<String, Strategy> 按类型标识分组。具体做法:在每个策略实现中定义一个 getType() 方法,在服务类构造时使用 Collectors.toMap() 构建策略映射。新增策略只需添加 @Component 注解,无需修改其他代码。
Q4:策略模式中,策略对象应该是有状态还是无状态的?
A:理想情况下,策略对象应该是无状态的,这样可以在多个 Context 之间共享(配合享元模式)。如果策略需要状态(如计数器、配置),应该通过 Context 传递参数,而不是在策略中保存状态。如果必须保存状态,则每次使用都需要创建新的策略实例。
Q5:策略模式 + 工厂模式 vs 策略模式 + Spring 注入,哪种更好?
A:Spring 注入更好。原因:(1) 自动发现:新增策略只需添加 @Component,Spring 自动注入,无需修改工厂代码;(2) 依赖注入:策略可以使用 Spring 的依赖注入功能(如注入 Repository、Service);(3) AOP 支持:策略可以享受 Spring AOP 的事务、日志等增强;(4) 生命周期管理:Spring 管理策略的 Bean 生命周期。工厂模式更适合非 Spring 环境或策略数量变化不频繁的场景。
