Skip to content

策略模式

策略模式用于把一组可替换的算法或业务处理逻辑封装起来,让调用方在运行时选择不同策略。它常用于支付方式、折扣规则、文件解析、消息发送、导出格式等场景。

为什么需要策略模式

如果所有业务差异都写在一个方法里,会出现大量 if/else。

java
public BigDecimal calculate(String memberType, BigDecimal price) {
    if ("VIP".equals(memberType)) {
        return price.multiply(new BigDecimal("0.8"));
    }
    if ("SVIP".equals(memberType)) {
        return price.multiply(new BigDecimal("0.7"));
    }
    if ("NEW_USER".equals(memberType)) {
        return price.subtract(new BigDecimal("10"));
    }
    return price;
}

问题:

  1. 新增规则要改老代码。
  2. 一个方法越来越长。
  3. 不同规则互相影响,测试困难。
  4. 不符合开闭原则。

策略模式把每种规则拆成独立类:

mermaid
flowchart TD
    A["DiscountService"] --> B["StrategyFactory"]
    B --> C{"memberType"}
    C -- "VIP" --> D["VipDiscountStrategy"]
    C -- "SVIP" --> E["SvipDiscountStrategy"]
    C -- "NEW_USER" --> F["NewUserDiscountStrategy"]

基本结构

mermaid
flowchart TD
    A["业务上下文 Context"] --> B["策略接口 Strategy"]
    B --> C["策略实现 A"]
    B --> D["策略实现 B"]
    B --> E["策略实现 C"]

角色:

角色作用
Strategy定义统一行为接口
ConcreteStrategy每个具体策略实现自己的算法
Context/Factory根据业务条件选择策略并调用

策略模式的原理

策略模式把“选择规则”和“执行规则”拆开:

  1. 调用方只提出业务类型,例如 VIPWECHATEXCEL
  2. 上下文或工厂根据类型找到一个策略对象。
  3. 业务真正变化的算法写在策略对象中。
  4. 调用方拿到统一结果,不关心内部算法。
mermaid
flowchart TD
    A["业务请求"] --> B["提取策略 key"]
    B --> C["策略注册表 Map"]
    C --> D{"找到策略"}
    D -- "否" --> E["默认策略或明确异常"]
    D -- "是" --> F["执行对应策略"]
    F --> G["返回统一结果"]

它解决的不是“代码里不能有 if”,而是当 if 的每个分支都代表一套会独立演进的业务规则时,把这些规则从一个大方法里拆出去。少量、稳定、非常简单的判断没有必要强行使用策略模式。

Java Demo

策略接口:

java
public interface DiscountStrategy {
    String type();

    BigDecimal calculate(BigDecimal price);
}

VIP 策略:

java
@Component
public class VipDiscountStrategy implements DiscountStrategy {
    @Override
    public String type() {
        return "VIP";
    }

    @Override
    public BigDecimal calculate(BigDecimal price) {
        return price.multiply(new BigDecimal("0.8"));
    }
}

新人策略:

java
@Component
public class NewUserDiscountStrategy implements DiscountStrategy {
    @Override
    public String type() {
        return "NEW_USER";
    }

    @Override
    public BigDecimal calculate(BigDecimal price) {
        return price.subtract(new BigDecimal("10")).max(BigDecimal.ZERO);
    }
}

策略工厂:

java
@Component
public class DiscountStrategyFactory {
    private final Map<String, DiscountStrategy> strategyMap;

    public DiscountStrategyFactory(List<DiscountStrategy> strategies) {
        this.strategyMap = strategies.stream()
            .collect(Collectors.toMap(DiscountStrategy::type, strategy -> strategy));
    }

    public DiscountStrategy get(String type) {
        DiscountStrategy strategy = strategyMap.get(type);
        if (strategy == null) {
            throw new IllegalArgumentException("不支持的会员类型: " + type);
        }
        return strategy;
    }
}

使用:

java
@Service
public class DiscountService {
    private final DiscountStrategyFactory strategyFactory;

    public DiscountService(DiscountStrategyFactory strategyFactory) {
        this.strategyFactory = strategyFactory;
    }

    public BigDecimal calculate(String memberType, BigDecimal price) {
        return strategyFactory.get(memberType).calculate(price);
    }
}

策略选择流程

mermaid
flowchart TD
    A["接收业务类型"] --> B["从 Map 查找策略"]
    B --> C{"策略是否存在"}
    C -- "否" --> D["抛出明确异常或走默认策略"]
    C -- "是" --> E["执行策略"]
    E --> F["返回结果"]

商业场景:消息通知策略

商业系统里经常需要发送通知:短信、邮件、站内信、企业微信、钉钉。通知这件事的目标相同,都是“把消息发给用户”,但不同渠道的参数、限流、失败处理完全不同。

如果写成一个方法:

java
public void send(String channel, Message message) {
    if ("SMS".equals(channel)) {
        // 调短信供应商
    } else if ("EMAIL".equals(channel)) {
        // 调邮件服务器
    } else if ("WECHAT_WORK".equals(channel)) {
        // 调企业微信
    }
}

后续会出现这些问题:

问题后果
每个渠道参数不同一个方法里到处判断和转换
失败处理不同短信要换供应商,邮件要重试,站内信要落库
发送频率不同限流规则互相污染
新增渠道频繁老方法不断被修改,回归风险高

使用策略模式后,每个渠道一个策略:

mermaid
flowchart TD
    A["通知服务"] --> B["NotificationStrategyFactory"]
    B --> C{"channel"}
    C -- "SMS" --> D["SmsNotificationStrategy"]
    C -- "EMAIL" --> E["EmailNotificationStrategy"]
    C -- "APP" --> F["AppNotificationStrategy"]
    D --> G["发送结果"]
    E --> G
    F --> G

策略接口:

java
public interface NotificationStrategy {
    String channel();

    SendResult send(Message message);
}

短信策略:

java
@Component
public class SmsNotificationStrategy implements NotificationStrategy {
    @Override
    public String channel() {
        return "SMS";
    }

    @Override
    public SendResult send(Message message) {
        // 真实项目中会做模板参数、签名、供应商调用、响应码转换
        System.out.println("send sms: " + message.getContent());
        return SendResult.success();
    }
}

邮件策略:

java
@Component
public class EmailNotificationStrategy implements NotificationStrategy {
    @Override
    public String channel() {
        return "EMAIL";
    }

    @Override
    public SendResult send(Message message) {
        System.out.println("send email: " + message.getContent());
        return SendResult.success();
    }
}

策略工厂:

java
@Component
public class NotificationStrategyFactory {
    private final Map<String, NotificationStrategy> strategyMap;

    public NotificationStrategyFactory(List<NotificationStrategy> strategies) {
        this.strategyMap = strategies.stream()
                .collect(Collectors.toMap(NotificationStrategy::channel, strategy -> strategy));
    }

    public NotificationStrategy getRequired(String channel) {
        NotificationStrategy strategy = strategyMap.get(channel);
        if (strategy == null) {
            throw new IllegalArgumentException("不支持的通知渠道: " + channel);
        }
        return strategy;
    }
}

业务服务:

java
@Service
public class NotificationService {
    private final NotificationStrategyFactory strategyFactory;

    public NotificationService(NotificationStrategyFactory strategyFactory) {
        this.strategyFactory = strategyFactory;
    }

    public SendResult send(String channel, Message message) {
        return strategyFactory.getRequired(channel).send(message);
    }
}

新增钉钉通知时,只新增 DingTalkNotificationStrategy,不改通知服务主流程。

策略模式要不要配默认策略

这取决于业务风险:

场景建议原因
支付、扣款、权限不建议默认策略,找不到就失败错用默认策略会产生资金或安全问题
折扣计算可以有默认原价策略找不到优惠规则时按原价比较安全
通知发送看业务要求重要通知应该失败告警,营销通知可降级
文件解析不建议默认策略解析错格式会造成数据污染

策略选择失败时不要默默返回 null,要么抛出明确异常,要么走业务上明确允许的默认策略。

使用边界

适合使用:

  1. 确实存在多种可替换规则。
  2. 规则会持续新增。
  3. 每种规则相对独立。
  4. 调用方只关心统一结果。

不适合使用:

  1. 只有一两个简单判断。
  2. 规则不会变化。
  3. 策略之间强依赖执行顺序。
  4. 抽象后反而让代码更难读。

和工厂模式区别

对比策略模式工厂模式
关注点行为怎么切换对象怎么创建或获取
主要目的隔离算法或业务规则封装创建逻辑
实际组合工厂返回某个策略策略执行业务

滥用风险

风险后果
规则很少也拆策略类数量增加,阅读成本变高
策略参数过于通用变成 Map/Object 到处强转
策略里互相调用失去独立性,调试困难
选择逻辑散落各处策略本身解耦了,选择过程又耦合

生产排查方法

策略模式的排查重点是:选错了策略,还是策略内部执行失败。

mermaid
flowchart TD
    A["业务结果异常"] --> B["检查策略 key"]
    B --> C{"key 是否正确"}
    C -- "否" --> D["检查入参、枚举转换、配置映射"]
    C -- "是" --> E["检查工厂注册表"]
    E --> F{"策略是否存在"}
    F -- "否" --> G["检查 Bean 扫描和 channel 重复"]
    F -- "是" --> H["进入具体策略日志"]
    H --> I["检查外部依赖、参数、异常处理"]

建议日志至少包含:

字段说明
strategyKey本次选择的策略 key
strategyClass实际执行的策略类
businessId订单号、用户 ID、任务 ID 等业务标识
costMs策略执行耗时
resultCode外部服务或内部规则返回码

面试标准回答

text
策略模式适合一件事有多种做法,并且这些做法会独立变化的场景。它把每种算法或业务规则封装成独立策略,调用方根据类型选择策略并执行统一接口。比如通知系统中短信、邮件、站内信都实现 NotificationStrategy,新增钉钉通知时只新增策略类,不改通知主流程。它的优点是减少大段 if/else、隔离变化、方便单测;缺点是类数量增加,策略选择规则必须集中管理,不能让选择逻辑散落各处。

追问补充:

追问回答方向
策略模式是不是为了消灭 if/else不是。它是为了隔离会持续变化的业务规则,简单稳定判断不用强行抽策略
策略和工厂区别工厂负责拿到对象,策略负责执行业务算法,项目中常一起使用
怎么处理找不到策略高风险业务抛异常,低风险且业务允许时使用默认策略
策略太多怎么管理统一 key、统一注册、统一日志,必要时按业务域拆包

小结

策略模式适合“同一件事有多种做法”的场景。它的价值是隔离变化,让新增规则尽量变成新增类,而不是修改一个越来越长的方法。但如果变化点不稳定或逻辑很简单,直接写清楚比提前抽象更好。