Skip to content

策略模式 (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);
        }
        // 每增加一种支付方式,都要修改此方法
        // 违反开闭原则
    }
}

问题:

  1. 新增算法需要修改原有代码,违反开闭原则
  2. if-else 分支过多,代码可读性差
  3. 算法与客户端耦合,无法独立测试
  4. 无法在运行时动态切换算法

三、结构

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);
    }
}

五、优缺点

优点

  1. 符合开闭原则:新增策略无需修改现有代码,只需新增一个策略类
  2. 消除 if-else:用多态替代条件判断,代码更清晰
  3. 算法可复用:策略可以在不同 Context 中共享
  4. 方便测试:每个策略可以独立进行单元测试
  5. 运行时切换:可以在运行时动态切换算法

缺点

  1. 类数量增加:每个策略都需要一个类,Strategy 过多时类膨胀
  2. 客户端需要了解策略:客户端需要知道有哪些策略并选择合适的策略
  3. 策略之间不通信:策略之间无法共享数据(除非通过 Context)
  4. 过度设计风险:对于只有少量算法的情况,策略模式可能过度设计

六、适用场景

  1. 多种算法/策略选择:支付方式、优惠计算、排序算法
  2. 消除 if-else:大量条件分支可以根据不同策略划分
  3. 算法需要动态切换:运行时根据条件选择不同算法
  4. 算法需要独立测试:每个策略独立开发和测试
  5. 规则引擎:根据不同的规则执行不同的逻辑
  6. 数据导出:Excel、PDF、CSV 不同导出格式

七、JDK / Spring 框架中的实际应用

框架应用位置说明
JDKjava.util.Comparator排序策略接口
JDKjava.util.concurrent.ThreadPoolExecutor拒绝策略 RejectedExecutionHandler
JDKjava.nio.file.FileVisitor文件访问策略
SpringResource 接口不同资源加载策略
SpringInstantiationStrategyBean 实例化策略
Spring SecurityPasswordEncoder密码加密策略
Spring SecurityAuthenticationProvider认证策略
Spring MVCViewResolver视图解析策略
MyBatisExecutorTypeSQL 执行策略(SIMPLE, REUSE, BATCH)

八、与其他模式的关系

与状态模式(重要对比)

  • 结构相似:类图几乎一样
  • 意图不同:策略模式关注"算法可替换"(客户端选择),状态模式关注"状态变化导致行为变化"(自行切换)
  • 生命周期:策略互不感知,状态之间相互感知可切换
  • 典型场景:策略 → 支付方式选择;状态 → 订单状态流转

与工厂模式

  • 策略模式常与工厂模式结合使用,工厂负责创建策略对象,策略模式负责执行算法

与模板方法模式

  • 策略模式通过组合实现算法替换,模板方法模式通过继承实现算法骨架
  • 策略模式更灵活(运行时切换),模板方法模式更简单(编译时确定)

与命令模式

  • 策略模式关注"如何做"(算法),命令模式关注"做什么"(请求封装)
  • 命令模式支持撤销,策略模式不支持

与享元模式

  • 无状态的策略对象可以使用享元模式共享,减少内存开销

九、面试常见问题

Q1:策略模式和状态模式有什么区别?

A:最核心的区别在于意图。策略模式中,策略之间互不感知,由客户端主动选择策略,关注的是"算法的可替换性";状态模式中,状态之间可以相互切换(如 CreatedStatepay() 后自行切换到 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 环境或策略数量变化不频繁的场景。