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

📅 2026/7/29 7:58:32
干掉成山的 if-else:工厂造、策略选,一文讲透两个模式的配合
大家好我是晚安code。你大概率见过这种支付代码一个 pay() 方法里 if/else 塞了 6 个分支微信、支付宝、银行卡各占一段每加一个渠道就再往里堆一块。问起来永远一句「赶进度先这样吧」。这种「先这样」最后都会变成「只能这样」。工厂模式和策略模式这俩一个给「创建对象」解耦一个给「选择算法」解耦配合起来正好干掉这种分支地狱。这篇我从生活类比一路讲到 Spring 源码一次讲透。先收藏慢慢看。一、先搞清楚设计模式到底是什么为什么要学设计模式不是炫技是前人踩坑踩出来的「套路」让你写出来的代码别人接手时不用骂街。设计模式Design Pattern前人针对反复出现的软件设计问题总结出的可复用解决方案。你可以把它理解成「代码界的菜谱」——同一道菜同一个问题照着成熟配方做不会翻车。那为什么非要学我给你三个最实在的理由第一是复用别人趟过的坑你不用再趟一遍第二是沟通团队里说一句「这里用工厂模式」大家都懂你在说什么省下半小时画图第三是可维护符合下面这条原则的代码加功能不用动老逻辑。开闭原则Open-Closed PrincipleOCP软件实体应该「对扩展开放对修改关闭」——加新功能时尽量加新代码而不是去改已经在跑的老代码。这条原则是后面两个模式共同的目标。打个生活化的比方盖楼不会每栋都从和泥开始你会用标准化的图纸和预制件设计模式就是代码世界里的预制件图纸。GoF 23 种模式里工厂模式和策略模式是出现频率最高的两个下面我一个个拆给你看。二、工厂模式把「new」这件事集中管起来工厂模式解决的核心痛点是——对象该怎么创建又不让调用方关心创建细节。工厂模式Factory Pattern把对象的创建过程封装进一个「工厂」角色里调用方只要告诉工厂「我要什么」不用自己 new、不用关心构造细节。类比就是去餐厅点餐你喊一句「来份宫保鸡丁」后厨怎么切、怎么炒、食材哪来的你一概不管上菜就完事。工厂模式其实分三种别被名字绕晕记住区别就行简单工厂一个工厂方法里 switch 一下返回不同对象。最常用但严格说不算 GoF 23 种。工厂方法每个产品配一个工厂类加新产品只加新类完全符合开闭原则。抽象工厂管一「族」产品比如一套前端组件要同时出深色版和浅色版。放到生活里再秒懂一遍你点外卖App 后面是个「订单工厂」不管它最终走的是哪家配送接口你只管点寄件时选顺丰还是圆通对你来说都是「叫个快递」背后是不同的快递工厂在创建运单。开发里这类场景太常见了根据配置创建不同数据库连接MySQL / PostgreSQL、日志组件按需切换控制台 / 文件 / 远程、根据消息类型创建不同处理器。核心都是「创建逻辑会变但调用方不想跟着变」。看一段最朴素的简单工厂Java示例// 示例把支付渠道的创建集中到工厂里publicinterfacePayChannel{voidpay(BigDecimalamount);}publicclassPayFactory{publicstaticPayChannelcreate(Stringtype){returnswitch(type){casewechat-newWechatPay();casealipay-newAlipayPay();default-thrownewIllegalArgumentException(未知渠道: type);};}}// 调用方不关心怎么 new只要结果PayChannelchPayFactory.create(alipay);ch.pay(BigDecimal.valueOf(99));调用方手里只有PayChannel接口根本不知道AlipayPay长啥样。工厂挡在客户端和具体产品中间客户端只依赖产品接口加新产品时改工厂、不动调用方到了 Spring工厂更是遍地都是。BeanFactory和ApplicationContext本身就是个超级大工厂——你调一句getBean(xxx)它帮你创建并返回对象这就是简单工厂的巨型版。FactoryBean接口更典型你实现getObject()方法自己控制怎么造 BeanSpring 最终拿到的就是你造出来的对象。集成 MyBatis、Dubbo 时那些代理对象基本都是靠FactoryBean造出来的。JDK 里也不少Calendar.getInstance()、NumberFormat.getInstance()、DriverManager.getConnection()骨子里都是工厂。三、策略模式用「换算法」干掉成山的 if-else策略模式解决的核心痛点是——同一件事有多种做法怎么做到想换就换、还不碰老代码。策略模式Strategy Pattern把一组做同一件事的不同算法各自封装成独立的类它们实现同一个接口因此可以互相替换。类比就是导航 App 算路线「最快」「最省钱」「避开拥堵」就是三个策略目的地不变换个策略就走不同的路。放到生活里再秒懂去收银台结账现金、刷卡、扫码是三种「支付策略」你选哪个都行但最终都是「完成付款」这一件事。开发里这类场景同样密集促销打折满减 / 打折 / 用券、风控校验不同等级用户走不同规则、消息推送短信 / 邮件 / 站内信。先看一段反面教材这种代码你大概率写过// 示例反面教材——越加越长的折扣方法publicBigDecimalcalc(Stringtype,BigDecimalprice){if(fullReduction.equals(type)){// 满减returnprice.compareTo(HUNDRED)0?price.subtract(TEN):price;}elseif(discount.equals(type)){// 打折returnprice.multiply(newBigDecimal(0.8));}elseif(coupon.equals(type)){// 优惠券returnprice.subtract(COUPON);}returnprice;}每加一种活动就得改这个方法、加一个分支几百行挤在一起老逻辑被反复触碰——典型的违反开闭原则。用策略模式重构一下// 示例策略接口 每个算法独立成类publicinterfaceDiscountStrategy{BigDecimalapply(BigDecimalprice);}publicclassDiscountStrategy8implementsDiscountStrategy{publicBigDecimalapply(BigDecimalprice){returnprice.multiply(newBigDecimal(0.8));}}// 调用方持有策略想换随时换互不影响DiscountStrategysnewDiscountStrategy8();s.apply(BigDecimal.valueOf(100));两种写法摆一起对比差距很明显对比项一坨 if-else策略模式加新算法改老方法、加分支新增一个策略类老代码不动可读性几百行挤一起每个算法独立各管各的单元测试分支互相影响难测每个策略单独测开闭原则不符合符合可能有人会问策略模式不就是把 if-else 换成map.get()吗有啥意义形式上像本质不同。map 只是查找手段真正的意义是「每个算法独立成类、可单独测试、可热插拔」。更关键的是它把「选哪个」和「怎么算」拆开了——选哪个交给调用方或工厂怎么算交给策略自己职责一清二楚。JDK 里最经典的策略是ThreadPoolExecutor的拒绝策略RejectedExecutionHandler线程池任务满了怎么办它给你四个策略——AbortPolicy抛异常、CallerRunsPolicy让调用线程自己跑、DiscardPolicy直接丢、DiscardOldestPolicy丢最老的任务。换一个 handler线程池行为完全不同这就是教科书级的策略模式。Spring 里也有Resource接口ClassPathResource、FileSystemResource、UrlResource都是它的实现访问不同位置的资源用不同策略Resource这层抽象把它们统一了起来。四、为什么它俩总是一起出现工厂造、策略选单用策略模式有个尴尬——调用方还得自己 new 具体策略结果又耦合回去了工厂模式正好补上这一刀。你看上面折扣的例子调用方写的是new DiscountStrategy8()它又得知道具体类名换策略就得改这行 new。这就好比导航 App 让你自己手写每条路线的代码那还策略个啥。把工厂请进来就顺了调用方只说「我要打折策略」工厂负责把对应的策略对象造出来递过去。策略管「算法怎么算」工厂管「对象怎么来」分工一明确if-else 就彻底没了落脚点。请求带着类型进来上下文找工厂要策略工厂从一堆策略里取对的那个统一调 pay开发里最经典的配合场景就是支付系统微信、支付宝、银行卡是三个支付策略再加新渠道比如 Apple Pay只是加一个策略类老代码一行不改。下面是后端最常用的 Spring 写法以 Spring 6.x、JDK 17 为例2026 年实测语法// 示例Spring 自动注入 Map本质就是个策略工厂publicinterfacePayStrategy{voidpay(BigDecimalamount);}Service(wechat)publicclassWechatPayimplementsPayStrategy{/* ... */}Service(alipay)publicclassAlipayPayimplementsPayStrategy{/* ... */}ComponentpublicclassPayContext{AutowiredprivateMapString,PayStrategystrategyMap;// Spring 按 Bean 名自动注入publicvoidpay(Stringtype,BigDecimalamount){PayStrategysstrategyMap.get(type);if(snull)thrownewIllegalArgumentException(不支持: type);s.pay(amount);}}这里的MapString, PayStrategy就是 Spring 帮你建好的工厂——所有实现了PayStrategy的 Bean 自动塞进 Mapkey 是 Bean 名。加 Apple Pay写一个Service(applepay)的类结束。没有 if-else、没有 switch完全符合开闭原则。可能有人会问这套在非 Spring 项目里还能用吗能。没有 Spring你就自己写个工厂类在构造方法或静态块里手动map.put(wechat, new WechatPay())注册策略效果一样。Spring 只是把「手动注册」这步用依赖注入自动化了省的是体力不是思想。在我看来工厂模式和策略模式不是两个孤立的面试考点而是一对搭档工厂把「创建」关进笼子策略把「选择」拆成零件合在一起你的代码才能做到加功能不动老逻辑。面试官爱问、Spring 源码里遍地都是恰恰说明它们是真的好用而不只是八股。下一篇我会聊聊「策略 工厂 模板方法」三件套以及怎么用枚举把策略注册也收编掉感兴趣的话先点个关注。我是晚安code持续分享编程干货。觉得有用的话记得点赞收藏和关注~也欢迎在评论区聊聊你项目里那些成山的 if-else最后是怎么被你干掉的