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

大家好,我是晚安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,示例):

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 造出来的。

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

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

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

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

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

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

java 复制代码
// 示例:反面教材------越加越长的折扣方法
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;
}

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

java 复制代码
// 示例:策略接口 + 每个算法独立成类
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 年实测语法):

java 复制代码
// 示例: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,最后是怎么被你干掉的?

相关推荐
feng尘1 小时前
深度解析布隆过滤器(Bloom Filter):原理、优缺点与 1000 万黑名单实战
后端·面试
大陈AI1 小时前
Docker Compose 前后端部署踩坑实录:3 个坑让我的容器反复 Exit(1)
后端
长大19881 小时前
MySQL 慢查询排查完整流程
后端
苏三说技术2 小时前
为什么越来越多人使用FastAPI?
后端
老孙讲技术2 小时前
业主半夜想看楼道监控,物业却说「去机房」?我用设备托管+轻应用,把小区摄像头嵌进了社区小程序
后端·物联网
geovindu2 小时前
CSharp: Breadth First Search Algorithm and Depth First Search Algorithm
开发语言·后端·算法·c#·.net·搜索算法
二月龙2 小时前
什么是事务四大特性?用业务案例通俗讲透 ACID
后端
Slice_cy3 小时前
Mint 自研框架设计与实现:从重复开发走向配置驱动(五)
前端·后端·架构
景同学3 小时前
把 AI 用到线上运维:可行、有效,前提是喂足信息——一次 Full GC 排障实录
java·人工智能·后端