Java 23 种设计模式:从踩坑到精通 | 策略模式 —— 算法族的封装与切换,告别 if-else

Java 23 种设计模式:从踩坑到精通 | 策略模式 —— 算法族的封装与切换,告别 if-else

摘要:当同一个操作有多种算法实现,且需要根据场景灵活切换时,if-elseswitch 会将所有算法混杂在一起,导致逻辑臃肿、扩展困难。策略模式将每个算法封装为独立的策略类,使它们可以相互替换,且算法的变化不影响使用算法的客户端。本文从电商促销策略的场景出发,完整讲解策略模式的原理、UML、代码实现、与状态模式的区别,并结合电子面单多平台架构实战、JDK 排序比较器、Spring 资源加载等应用,帮你掌握“算法对象化”的设计精髓。

🗺️ 本文阅读地图(3 分钟速览)

  • 为什么一坨 if-else 促销代码一定会炸?
  • 策略模式三大角色拆解
  • 手写促销策略引擎(满减、打折、立减)
  • Lambda 简化策略模式
  • 实战案例:电子面单多平台架构中的三层策略应用
  • JDK Comparator / Spring ResourceLoader 如何体现
  • 面试必问:策略 vs 状态,到底怎么区分?

📖 《Java 23 种设计模式:从踩坑到精通》
开篇:系列介绍与目录 | 上一篇:状态模式 | 当前:策略模式 | 下一篇:模版方法
🔗 返回系列总目录



1. 从“促销活动”的一堆 if-else 说起

电商系统经常需要搞促销:满减、打折、立减、赠品…… 每一种促销方式的计算逻辑都不同。如果直接在订单结算方法里写死:

double calculate(Order order, String promotionType) {
    if ("满减".equals(promotionType)) {
        return order.getTotal() - (order.getTotal() / 200) * 50;
    } else if ("打折".equals(promotionType)) {
        return order.getTotal() * 0.8;
    } else if ("立减".equals(promotionType)) {
        return order.getTotal() - 30;
    }
    return order.getTotal();
}

这种方式有明显的痛点:

  • 所有算法堆在一起,calculate 方法越来越长;
  • 新增一种促销就要修改原有代码,违反开闭原则;
  • 促销策略无法在运行时动态切换,也无法灵活组合。

策略模式(Strategy Pattern)正是为这种“同一操作、多种算法”的场景而生:它定义一系列算法,把它们一个个封装起来,并且使它们可以相互替换。策略模式让算法的变化独立于使用算法的客户。

🏭 实战视角:不只是促销计算,在多平台电子面单系统中,我们也遇到了几乎一模一样的问题——十几个电商平台(奇门、抖音、京东、拼多多……)都需要“取号”,但每个平台的API接口、签名算法、请求格式完全不同。如果用 if-else 把所有平台的逻辑堆在一起,代码会迅速膨胀到无法维护。我们正是用策略模式解决了这个问题。在本节末尾,我会用这个真实案例来展示策略模式在复杂业务中的落地。

1.1 你的场景该不该用策略模式?

判断标准是 → 用策略模式否 → 用其他方式
同一个操作有多种实现方式,且可能动态切换
算法逻辑复杂,相互独立,需要避免冗长的条件语句
未来需要增加新的算法,且不希望修改原有代码
只有两三种固定不变的简单分支直接使用 if-else 即可

2. 模式定义与 UML 结构

策略模式 定义一系列算法,将每一个算法封装起来,并让它们可以相互替换。策略模式让算法的变化独立于使用算法的客户。它属于 行为型设计模式

策略模式

图文解析(配合上述 UML 图)

策略模式的核心角色:

  • 抽象策略(Strategy:定义所有支持的算法的公共接口,通常只有一个方法如 calculate()algorithm()
  • 具体策略(ConcreteStrategyA / ConcreteStrategyB:实现具体的算法,每个策略类封装一种独立的算法变体。
  • 上下文(Context:持有策略对象的引用,负责调用策略算法。上下文不关心具体是哪个策略,只需知道如何触发计算。

核心机制:策略模式把“如何做”从“谁要做”中分离出来。上下文对象只知道“我要执行一个算法”,但不知道具体是哪个算法。具体算法由客户端在运行时注入。


3. 代码实现:电商促销策略

3.1 抽象策略

public interface PromotionStrategy {
    double calculate(Order order);
}

💬 白话:所有促销策略都必须能“算钱”。

3.2 具体策略

public class FullReductionStrategy implements PromotionStrategy {
    @Override
    public double calculate(Order order) {
        double discount = Math.floor(order.getTotal() / 200) * 50;
        System.out.println("应用满减策略:满200减50,减免" + discount + "元");
        return order.getTotal() - discount;
    }
}

public class DiscountStrategy implements PromotionStrategy {
    @Override
    public double calculate(Order order) {
        System.out.println("应用打折策略:8折");
        return order.getTotal() * 0.8;
    }
}

public class DirectReductionStrategy implements PromotionStrategy {
    @Override
    public double calculate(Order order) {
        System.out.println("应用立减策略:立减30元");
        return order.getTotal() - 30;
    }
}

💬 白话:每种促销是一个独立的类,互不干扰,修改满减逻辑不会影响打折。

3.3 上下文:订单结算

public class Order {
    private double total;
    private PromotionStrategy strategy;

    public Order(double total) { this.total = total; }
    public double getTotal() { return total; }

    public void setPromotionStrategy(PromotionStrategy strategy) {
        this.strategy = strategy;
    }

    public double checkout() {
        if (strategy == null) {
            return total;
        }
        return strategy.calculate(this);
    }
}

💬 白话:订单不关心用哪种促销,它只知道有促销就用促销算钱,没有就原价。

3.4 客户端

Order order = new Order(500);

// 使用满减
order.setPromotionStrategy(new FullReductionStrategy());
System.out.println("实付金额:" + order.checkout() + "\n");

// 切换为打折
order.setPromotionStrategy(new DiscountStrategy());
System.out.println("实付金额:" + order.checkout() + "\n");

// 切换为立减
order.setPromotionStrategy(new DirectReductionStrategy());
System.out.println("实付金额:" + order.checkout());

新增一种促销(如“赠品策略”),只需新增一个 GiftStrategy 类,客户端注入即可,订单结算代码零修改


4. Lambda 简化策略模式(Java 8+)

如果策略接口只有一个抽象方法,那它就是函数式接口,可以用 Lambda 表达式直接替代具体策略类,减少类数量。

Order order = new Order(500);

// 满减策略
order.setPromotionStrategy(o -> {
    double discount = Math.floor(o.getTotal() / 200) * 50;
    return o.getTotal() - discount;
});

// 打折策略
order.setPromotionStrategy(o -> o.getTotal() * 0.8);

System.out.println(order.checkout());

💬 白话:策略逻辑简单时,不需要专门写一个类,Lambda 直接搞定。


5. 实战案例:电子面单多平台架构中的三层策略

理论学完了,来看看策略模式在真实项目中是怎么用的。在我们的多平台电子面单系统中,需要对接十几个电商平台取号。每个平台的API、签名、响应格式完全不同。如果写一堆 if-else 分支,代码会迅速膨胀。

我们的做法是:把“取号”这个操作拆成三个策略接口——请求构建、响应解析、异常判断。每个平台(奇门、抖音、京东……)都提供自己的一套实现。

5.1 三层策略接口定义

// 请求构建策略:每个平台组装各自的请求
public interface RequestStrategy {
    Object buildRequest(WaybillContext ctx);
}

// 响应解析策略:每个平台解析各自的返回
public interface ParseStrategy {
    List<TocPickTicketWayBillDetailsNew> parseResponse(WaybillContext ctx, String response);
}

// 异常判断策略:每个平台判断各自的成功/失败
public interface ExceptionStrategy {
    boolean isBusinessSuccess(String response);
    String extractErrorMsg(String response);
}

5.2 各平台实现(以奇门和抖音为例)

// 奇门平台:构建淘宝SDK请求
public class QiMenRequestStrategy implements RequestStrategy {
    @Override
    public Object buildRequest(WaybillContext ctx) {
        return QiMenWaybillBuilder.buildRequest(ctx);
    }
}

// 抖音平台:构建HTTP JSON请求
public class DouYinRequestStrategy implements RequestStrategy {
    @Override
    public Object buildRequest(WaybillContext ctx) {
        return DouYinWaybillBuilder.buildRequest(ctx);
    }
}

5.3 模板编排器(上下文)

public class WaybillFetchTemplate {
    private final RequestStrategy requestStrategy;
    private final ParseStrategy parseStrategy;
    private final ExceptionStrategy exceptionStrategy;

    // 通过构造函数注入三个策略
    public WaybillFetchTemplate(RequestStrategy req, ParseStrategy parse, ExceptionStrategy ex) {
        this.requestStrategy = req;
        this.parseStrategy = parse;
        this.exceptionStrategy = ex;
    }

    public boolean execute(WaybillContext ctx) {
        Object request = requestStrategy.buildRequest(ctx);     // 策略:构建请求
        String response = apiInvoker.invoke(ctx, request);     // 调用API
        if (!exceptionStrategy.isBusinessSuccess(response)) {  // 策略:判断成功
            markException(ctx.getTicket(), exceptionStrategy.extractErrorMsg(response));
            return false;
        }
        List<Detail> details = parseStrategy.parseResponse(ctx, response); // 策略:解析响应
        persistence.saveAndBind(ctx.getTicket(), details);
        return true;
    }
}

💡 关键设计WaybillFetchTemplate 只依赖接口,不关心是奇门还是抖音。新增一个平台(比如快手),只需新增三个策略实现类并注册到工厂,模板编排器代码零改动。这就是策略模式带来的开闭原则实践。

5.4 策略模式与工厂模式配合

策略模式解决了“怎么换”的问题,但“换哪个”通常需要工厂模式来配合。在我们的架构中,StrategyFactory 根据平台编码返回对应的策略实例:

// 根据平台编码获取策略
RequestStrategy req = strategyFactory.getRequestStrategy(platformCode, original);
ParseStrategy parse = strategyFactory.getParseStrategy(platformCode, original);
ExceptionStrategy ex = strategyFactory.getExceptionStrategy(platformCode, original);

// 注入到模板中
WaybillFetchTemplate template = new WaybillFetchTemplate(req, parse, ex);
template.execute(ctx);

📖 这套架构的完整设计、复合Key路由机制、以及如何用策略模式+工厂模式配合实现“新增平台零改动核心代码”,详见电子面单实战系列的《多平台统一架构设计》和《策略工厂复合Key路由改造》。


6. 代码实现:文件排序策略(与 Comparator 对比)

文件管理器中,用户可以按名称、大小、修改日期排序文件。这天然适合策略模式,而且 JDK 的 Comparator 本身就是策略模式的经典实现。

6.1 抽象策略

public interface SortStrategy {
    void sort(List<FileInfo> files);
}

6.2 具体策略

public class SortByName implements SortStrategy {
    @Override
    public void sort(List<FileInfo> files) {
        files.sort(Comparator.comparing(FileInfo::getName));
        System.out.println("按名称排序完成");
    }
}

public class SortBySize implements SortStrategy {
    @Override
    public void sort(List<FileInfo> files) {
        files.sort(Comparator.comparingLong(FileInfo::getSize));
        System.out.println("按大小排序完成");
    }
}

public class SortByDate implements SortStrategy {
    @Override
    public void sort(List<FileInfo> files) {
        files.sort(Comparator.comparing(FileInfo::getModifiedDate));
        System.out.println("按修改日期排序完成");
    }
}

💬 白话Comparator 本身就是策略接口,comparing() 方法返回的就是具体策略。

6.3 上下文:文件管理器

public class FileManager {
    private SortStrategy sortStrategy;

    public void setSortStrategy(SortStrategy sortStrategy) {
        this.sortStrategy = sortStrategy;
    }

    public void sortFiles(List<FileInfo> files) {
        if (sortStrategy != null) {
            sortStrategy.sort(files);
        }
    }
}

7. 策略模式 vs 状态模式

这是面试中极容易混淆的一对:

对比维度策略模式状态模式
目的替换算法根据内部状态自动改变行为
谁决定切换客户端显式选择策略状态类自己决定何时切换到另一个状态
关注点算法的替换与组合状态之间的流转和转换
上下文感知策略通常不知道上下文的存在状态知道上下文的存在,并可能调用上下文方法
典型应用Comparator、支付方式、折扣策略订单状态机、线程状态、工单流转

💡 简单记忆:策略模式是“主动选择算法”,状态模式是“被动切换行为”。策略的切换由客户端决定,状态的切换由状态对象自己决定。


8. 优缺点一览

优点缺点
开闭原则:新增策略无需修改上下文,只需新增策略类类数量增加:每个策略一个类,策略多时类膨胀
消除条件判断:避免了大量 if-elseswitch客户端必须知道策略差异:需要了解各策略特点才能正确选择
策略可动态切换:运行时根据条件替换策略策略间通信成本:如果策略需要与上下文复杂交互,需额外设计
代码复用:策略类独立,可被多个上下文复用增加对象数量:每个策略都是一个对象,可能增加内存开销

9. 框架与实践中的应用

9.1 JDK:Comparator 接口

java.util.Comparator 是策略模式的教科书级应用。通过传入不同的 Comparator 实现,TreeSetCollections.sort() 等方法可以在不修改数据结构的情况下改变排序规则。

List<String> list = Arrays.asList("apple", "banana", "cherry");
list.sort(Comparator.naturalOrder());  // 正序
list.sort(Comparator.reverseOrder());  // 倒序

9.2 Spring 资源加载策略

Spring 的 ResourceLoader 可以根据资源路径前缀(如 classpath:file:)选择不同的资源加载策略,本质上是策略模式。

9.3 支付系统中的支付方式切换

电商平台支持支付宝、微信、银行卡等多种支付方式,每种支付方式封装为一个策略,用户在结算时选择,系统在运行时动态调用对应策略。


10. 面试必问 + 面试官追问连环炮

基础必问

  • 策略模式与状态模式的区别? → 策略由客户端决定切换,状态由状态类自己决定转换。
  • JDK 中哪里用了策略模式?ComparatorThreadUncaughtExceptionHandler
  • 策略模式如何消除 if-else? → 将每个分支算法封装为独立的策略类,通过多态调用,客户端只需注入当前策略。

面试官追问

  • “策略模式会导致类膨胀,怎么解决?”
    👉 对于逻辑简单的策略,使用 Lambda 表达式或函数式接口直接内联,避免创建独立类。
  • “策略模式和命令模式有什么区别?”
    👉 策略关注算法的封装与替换,命令关注请求的封装与调用者解耦。命令通常支持撤销,策略不关心。
  • “你在项目中用过策略模式吗?能举个具体的例子吗?”
    👉 可以讲电子面单的三层策略设计——为什么拆分、怎么配合工厂模式、新增平台零改动核心流程。这个案例既有代码细节又有架构高度,比背概念强十倍。完整的架构设计见电子面单实战系列《多平台统一架构设计》。

🎉 恭喜:如果你能立刻说出 Comparator 是策略模式,并清楚策略与状态的本质区别,还能用电子面单案例回答“项目里怎么用的”,你已经掌握了行为型模式中最常用的“算法替换”设计。


11. 六大设计原则在策略模式中的体现

设计原则在策略模式中的体现
单一职责原则(SRP)每个策略类只负责一种算法实现
开闭原则(OCP)新增策略无需修改上下文和原有策略,只需新增策略类
里氏替换原则(LSP)所有策略类都实现 Strategy 接口,可无缝替换
依赖倒置原则(DIP)上下文依赖抽象 Strategy 接口,不依赖具体策略
接口隔离原则(ISP)Strategy 接口只定义算法方法,精简无冗余
迪米特法则(LoD)上下文只知道策略接口,不了解策略内部实现

🧭 《Java 23 种设计模式:从踩坑到精通》快速导航


🏭 实战配套:电商多平台电子面单对接实战

本文第5节展示的电子面单三层策略架构,在我们的电子面单实战系列中有完整的落地代码和设计演进过程。如果你对以下问题感兴趣,推荐延伸阅读:

  • 策略模式 + 工厂模式:如何配合实现复合Key路由,解决同一平台下多子渠道的策略选择?
  • 策略模式 + 模板方法模式:编排器如何用组合方式固定流程骨架,策略只填差异步骤?
  • 新增平台零改动核心代码:如何实现真正的开闭原则?

📖 《电商多平台电子面单对接实战》

💡 学习建议:设计模式系列讲“为什么这么用”,电子面单系列讲“怎么用”。两者搭配,理论+实战闭环。

🔔 关注《Java 23 种设计模式:从踩坑到精通》,用 25 篇文章彻底吃透设计模式。
📦 福利预告:全系列代码及 UML 源码将在完结时统一打包开放,点击「关注」「收藏」第一时间获取。
🚀 下一篇:模板方法模式:定义算法骨架,交给子类填充细节!🚧 即将发布,敬请关注!

📌 除了设计模式,我也在深挖智能物流实战(WMS、托盘调度、机器学习落地)。欢迎点击头像,看看专栏 《出版社物流WMS智能调度实战》。技术相通,思路可鉴。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值