干掉成山的 if-else:工厂造、策略选,一文讲透两个模式的配合

简介: 写给被成堆 if-else 折磨的后端同学:拆开工厂模式和策略模式,搞懂创建与选择的解耦,配合 Spring 实战消灭分支地狱。

大家好,我是晚安code。

你大概率见过这种支付代码:一个 pay() 方法里 if/else 塞了 6 个分支,微信、支付宝、银行卡各占一段,每加一个渠道就再往里堆一块。问起来永远一句「赶进度,先这样吧」。

这种「先这样」最后都会变成「只能这样」。工厂模式和策略模式这俩,一个给「创建对象」解耦,一个给「选择算法」解耦,配合起来正好干掉这种分支地狱。这篇我从生活类比一路讲到 Spring 源码,一次讲透。先收藏,慢慢看。

一、先搞清楚:设计模式到底是什么,为什么要学

设计模式不是炫技,是前人踩坑踩出来的「套路」,让你写出来的代码别人接手时不用骂街。

设计模式(Design Pattern):前人针对反复出现的软件设计问题,总结出的可复用解决方案。你可以把它理解成「代码界的菜谱」——同一道菜(同一个问题),照着成熟配方做,不会翻车。

那为什么非要学?我给你三个最实在的理由:第一是复用,别人趟过的坑你不用再趟一遍;第二是沟通,团队里说一句「这里用工厂模式」,大家都懂你在说什么,省下半小时画图;第三是可维护,符合下面这条原则的代码,加功能不用动老逻辑。

开闭原则(Open-Closed Principle,OCP):软件实体应该「对扩展开放,对修改关闭」——加新功能时尽量加新代码,而不是去改已经在跑的老代码。这条原则是后面两个模式共同的目标。

打个生活化的比方:盖楼不会每栋都从和泥开始,你会用标准化的图纸和预制件;设计模式就是代码世界里的预制件图纸。GoF 23 种模式里,工厂模式和策略模式是出现频率最高的两个,下面我一个个拆给你看。

二、工厂模式:把「new」这件事集中管起来

工厂模式解决的核心痛点是——对象该怎么创建,又不让调用方关心创建细节。

工厂模式(Factory Pattern):把对象的创建过程封装进一个「工厂」角色里,调用方只要告诉工厂「我要什么」,不用自己 new、不用关心构造细节。类比就是去餐厅点餐:你喊一句「来份宫保鸡丁」,后厨怎么切、怎么炒、食材哪来的,你一概不管,上菜就完事。

工厂模式生活类比:餐厅点餐

工厂模式其实分三种,别被名字绕晕,记住区别就行:

  • 简单工厂:一个工厂方法里 switch 一下,返回不同对象。最常用,但严格说不算 GoF 23 种。
  • 工厂方法:每个产品配一个工厂类,加新产品只加新类,完全符合开闭原则。
  • 抽象工厂:管一「族」产品,比如一套前端组件要同时出深色版和浅色版。

放到生活里再秒懂一遍:你点外卖,App 后面是个「订单工厂」,不管它最终走的是哪家配送接口,你只管点;寄件时选顺丰还是圆通,对你来说都是「叫个快递」,背后是不同的快递工厂在创建运单。

开发里这类场景太常见了:根据配置创建不同数据库连接(MySQL / PostgreSQL)、日志组件按需切换(控制台 / 文件 / 远程)、根据消息类型创建不同处理器。核心都是「创建逻辑会变,但调用方不想跟着变」。

看一段最朴素的简单工厂(Java,示例):

// 示例:把支付渠道的创建集中到工厂里
public interface PayChannel {
    void pay(BigDecimal amount); }

public class PayFactory {
   
    public static PayChannel create(String type) {
   
        return switch (type) {
   
            case "wechat" -> new WechatPay();
            case "alipay" -> new AlipayPay();
            default       -> throw new IllegalArgumentException("未知渠道: " + type);
        };
    }
}

// 调用方:不关心怎么 new,只要结果
PayChannel ch = PayFactory.create("alipay");
ch.pay(BigDecimal.valueOf(99));

调用方手里只有 PayChannel 接口,根本不知道 AlipayPay 长啥样。工厂挡在客户端和具体产品中间,客户端只依赖产品接口,加新产品时改工厂、不动调用方:

工厂模式结构示意图:客户端 → 工厂 → 具体产品,客户端只依赖产品接口

到了 Spring,工厂更是遍地都是。BeanFactoryApplicationContext 本身就是个超级大工厂——你调一句 getBean("xxx"),它帮你创建并返回对象,这就是简单工厂的巨型版。FactoryBean 接口更典型:你实现 getObject() 方法自己控制怎么造 Bean,Spring 最终拿到的就是你造出来的对象。集成 MyBatis、Dubbo 时那些代理对象,基本都是靠 FactoryBean 造出来的。

Spring 中的工厂:BeanFactory / FactoryBean

JDK 里也不少:Calendar.getInstance()NumberFormat.getInstance()DriverManager.getConnection(),骨子里都是工厂。

三、策略模式:用「换算法」干掉成山的 if-else

策略模式解决的核心痛点是——同一件事有多种做法,怎么做到想换就换、还不碰老代码。

策略模式(Strategy Pattern):把一组做同一件事的不同算法,各自封装成独立的类,它们实现同一个接口,因此可以互相替换。类比就是导航 App 算路线:「最快」「最省钱」「避开拥堵」就是三个策略,目的地不变,换个策略就走不同的路。

放到生活里再秒懂:去收银台结账,现金、刷卡、扫码是三种「支付策略」,你选哪个都行,但最终都是「完成付款」这一件事。开发里这类场景同样密集:促销打折(满减 / 打折 / 用券)、风控校验(不同等级用户走不同规则)、消息推送(短信 / 邮件 / 站内信)。

先看一段反面教材,这种代码你大概率写过:

// 示例:反面教材——越加越长的折扣方法
public BigDecimal calc(String type, BigDecimal price) {
   
    if ("fullReduction".equals(type)) {
         // 满减
        return price.compareTo(HUNDRED) > 0 ? price.subtract(TEN) : price;
    } else if ("discount".equals(type)) {
       // 打折
        return price.multiply(new BigDecimal("0.8"));
    } else if ("coupon".equals(type)) {
         // 优惠券
        return price.subtract(COUPON);
    }
    return price;
}

每加一种活动,就得改这个方法、加一个分支,几百行挤在一起,老逻辑被反复触碰——典型的违反开闭原则。用策略模式重构一下:

// 示例:策略接口 + 每个算法独立成类
public interface DiscountStrategy {
   
    BigDecimal apply(BigDecimal price);
}

public class DiscountStrategy8 implements DiscountStrategy {
   
    public BigDecimal apply(BigDecimal price) {
    return price.multiply(new BigDecimal("0.8")); }
}

// 调用方持有策略,想换随时换,互不影响
DiscountStrategy s = new DiscountStrategy8();
s.apply(BigDecimal.valueOf(100));

两种写法摆一起对比,差距很明显:

对比项 一坨 if-else 策略模式
加新算法 改老方法、加分支 新增一个策略类,老代码不动
可读性 几百行挤一起 每个算法独立,各管各的
单元测试 分支互相影响,难测 每个策略单独测
开闭原则 不符合 符合

可能有人会问:策略模式不就是把 if-else 换成 map.get() 吗,有啥意义?

形式上像,本质不同。map 只是查找手段;真正的意义是「每个算法独立成类、可单独测试、可热插拔」。更关键的是,它把「选哪个」和「怎么算」拆开了——选哪个交给调用方或工厂,怎么算交给策略自己,职责一清二楚。

JDK 里最经典的策略,是 ThreadPoolExecutor 的拒绝策略 RejectedExecutionHandler:线程池任务满了怎么办?它给你四个策略——AbortPolicy(抛异常)、CallerRunsPolicy(让调用线程自己跑)、DiscardPolicy(直接丢)、DiscardOldestPolicy(丢最老的任务)。换一个 handler,线程池行为完全不同,这就是教科书级的策略模式。

Spring 里也有:Resource 接口,ClassPathResourceFileSystemResourceUrlResource 都是它的实现,访问不同位置的资源用不同策略,Resource 这层抽象把它们统一了起来。

四、为什么它俩总是一起出现:工厂造、策略选

单用策略模式有个尴尬——调用方还得自己 new 具体策略,结果又耦合回去了;工厂模式正好补上这一刀。

你看上面折扣的例子,调用方写的是 new DiscountStrategy8(),它又得知道具体类名,换策略就得改这行 new。这就好比导航 App 让你自己手写每条路线的代码,那还策略个啥。

把工厂请进来就顺了:调用方只说「我要打折策略」,工厂负责把对应的策略对象造出来递过去。策略管「算法怎么算」,工厂管「对象怎么来」,分工一明确,if-else 就彻底没了落脚点。请求带着类型进来,上下文找工厂要策略,工厂从一堆策略里取对的那个,统一调 pay:

工厂 + 策略配合示意图:请求 → 上下文 → 工厂 → 策略集合 → 具体策略

开发里最经典的配合场景就是支付系统:微信、支付宝、银行卡是三个支付策略,再加新渠道(比如 Apple Pay)只是加一个策略类,老代码一行不改。下面是后端最常用的 Spring 写法(以 Spring 6.x、JDK 17 为例,2026 年实测语法):

// 示例:Spring 自动注入 Map,本质就是个策略工厂
public interface PayStrategy {
    void pay(BigDecimal amount); }

@Service("wechat") public class WechatPay implements PayStrategy {
    /* ... */ }
@Service("alipay") public class AlipayPay implements PayStrategy {
    /* ... */ }

@Component
public class PayContext {
   
    @Autowired
    private Map<String, PayStrategy> strategyMap;  // Spring 按 Bean 名自动注入

    public void pay(String type, BigDecimal amount) {
   
        PayStrategy s = strategyMap.get(type);
        if (s == null) throw new IllegalArgumentException("不支持: " + type);
        s.pay(amount);
    }
}

这里的 Map<String, PayStrategy> 就是 Spring 帮你建好的工厂——所有实现了 PayStrategy 的 Bean 自动塞进 Map,key 是 Bean 名。加 Apple Pay?写一个 @Service("applepay") 的类,结束。没有 if-else、没有 switch,完全符合开闭原则。

可能有人会问:这套在非 Spring 项目里还能用吗?

能。没有 Spring,你就自己写个工厂类,在构造方法或静态块里手动 map.put("wechat", new WechatPay()) 注册策略,效果一样。

Spring 只是把「手动注册」这步用依赖注入自动化了,省的是体力,不是思想。

在我看来,工厂模式和策略模式不是两个孤立的面试考点,而是一对搭档:工厂把「创建」关进笼子,策略把「选择」拆成零件,合在一起,你的代码才能做到加功能不动老逻辑。面试官爱问、Spring 源码里遍地都是,恰恰说明它们是真的好用,而不只是八股。下一篇我会聊聊「策略 + 工厂 + 模板方法」三件套,以及怎么用枚举把策略注册也收编掉,感兴趣的话先点个关注。


我是晚安code,持续分享编程干货。觉得有用的话记得点赞收藏和关注~也欢迎在评论区聊聊:你项目里那些成山的 if-else,最后是怎么被你干掉的?

目录
相关文章
|
2月前
|
机器学习/深度学习 缓存 人工智能
阿里云百炼 Kimi K3 模型详解:多模态能力、限流参数、调用价格一览
全球首个开源3万亿级大模型Kimi K3正式上线阿里云百炼平台。该模型由月之暗面研发,参数达2.8万亿,支持100万Token超长上下文与原生视觉理解,具备文本生成、多模态推理和深度思考能力,输入定价20元/百万Token(缓存命中仅2元)。在阿里云百炼官网:https://t.aliyun.com/U/fPVHqY 免费领取千万Tokens
|
1月前
|
消息中间件 缓存 NoSQL
RocketMQ 延迟消息实战:延迟双删策略解决 Redis 缓存一致性
RocketMQ 延迟消息怎么用?本文从本地搭建开始,用订单超时案例讲透延迟消息与延迟双删,解决 Redis 缓存一致性难题。
152 2
|
1天前
|
缓存 分布式计算 安全
GPT-6 Astra 实测:贵 2.5 倍、编码追平 Claude,值吗?
GPT-6 Astra 来了,API 输入 $10、输出 $50/百万 token,约 Sol 的 2.5 倍。贵在哪、强在哪、值不值升级,一篇拆完。
123 0
GPT-6 Astra 实测:贵 2.5 倍、编码追平 Claude,值吗?
|
2月前
|
存储 数据采集 数据处理
基于 YOLO11 的遥感航拍林业树种检测:从数据准备到云上训练实践
本文介绍基于YOLO11的遥感航拍林业树种检测全流程:涵盖8类树种、1.8万+图像的数据集构建,Label Studio标注规范,云上存储与版本管理实践,以及YOLO格式适配、训练调优与模型评估方法,助力林业智能化调查。(239字)
基于 YOLO11 的遥感航拍林业树种检测:从数据准备到云上训练实践
|
1月前
|
运维 Java 调度
XXL-JOB 分布式定时任务框架:任务分片、失败重试、调度中心一次讲透
单机 @Scheduled 扛不住分布式定时任务?拆解 XXL-JOB 调度中心与执行器架构、任务分片、失败重试机制,附 30 分钟接入示例。
241 0
XXL-JOB 分布式定时任务框架:任务分片、失败重试、调度中心一次讲透
|
2月前
|
自然语言处理 关系型数据库 MySQL
Elasticsearch 入门教程:核心原理、Docker 部署与中文搜索实战
适合想快速上手 Elasticsearch 的后端开发者和搜索服务初学者。读完你会理解 ES 为什么搜得快、怎么在本地搭一套完整环境、怎么写查询、以及怎么把 MySQL 数据同步到 ES。
528 2
Elasticsearch 入门教程:核心原理、Docker 部署与中文搜索实战
|
2月前
|
数据采集 监控 供应链
评价与销量分析:除了价格,API还能看什么?
本文探讨如何通过评价、销量、竞品、用户行为等非价格API数据,构建多维产品洞察体系。结合情感分析、时间序列、漏斗转化等技术,实现口碑监控、需求预测与策略优化,助力数据驱动的产品决策。(239字)
|
2月前
|
存储 人工智能 安全
千景 3DVR 全景:轻量化实景数字孪生,打造制造业云端验厂数字化方案
工业数字化转型进入轻量化落地阶段,传统线下验厂、二维宣传存在成本高、真实感不足等痛点。千景 3DVR 全景依托实景三维重建技术,1:1 复刻工厂车间、产线、仓储全场景,搭载交互热点、语音导览、多终端云端分发能力,结合阿里云工业云实现轻量化部署。方案可落地云端远程验厂、企业品牌营销、新员工岗前培训等场景,为中小制造企业提供低成本、可复用的实景数字孪生解决方案,有效降低商务接待成本,提升 B 端客户信任与转化效率。
244 1
|
2月前
|
关系型数据库 MySQL 分布式数据库
PolarDB MySQL版 V2.0轻量版:精简模式(PolarFlex)版本发布日志
阿里云PolarDB数据库管理软件(MySQL版)V2.0,简称 PolarDB MySQL 版 V2.0;100%兼容 MySQL,产品具有多主多写、多活容灾、HTAP 等特性,交易性能最高可达开源数据库的6倍,分析性能最高可达开源数据库的400倍,TCO 低于自建数据库50%。