
1. 为什么这两个模式总被放在一起考又总被写错“工厂模式和策略模式区别以及使用”——这行字我见过太多次了Java期末考试卷子最后一道大题、校招技术面白板题、实习生转正答辩PPT第12页、甚至某厂内部设计规范文档的附录B。但奇怪的是每次看到同学或新人写的对比十有八九会把“谁创建对象”和“谁决定行为”混成一团。有人写“工厂模式也做选择策略模式也new对象”还有人直接画个UML图配文“二者都解耦”然后戛然而止。这背后不是概念记不牢而是没摸清两个模式在真实代码里的物理边界。工厂模式解决的是对象诞生那一刻的控制权问题——就像产线上的模具工只管按订单压出合格零件不管零件装进哪台机器、怎么运转策略模式解决的是对象运行时的行为切换问题——像汽车的驾驶模式按钮引擎、变速箱、转向系统全都不变但ECU一换逻辑运动模式就输出激进扭矩经济模式立刻收着走。一个管“生”一个管“用”一个在new()执行前就锁死一个在method()调用时才动态加载。我带过三届校招生让他们各自用工厂策略搭一个电商促销系统。结果80%的人第一版代码里DiscountStrategyFactory里直接new了FullReductionStrategy还美其名曰“组合使用”。这不是组合这是把两套独立控制系统焊死在一块儿——一旦要加个“满300减50”的新策略就得改工厂类违反开闭原则而真正的解法是让工厂只负责造出“能执行策略的上下文容器”策略本身由外部注入或配置驱动。这种认知偏差根源在于没亲手在日志里打过断点看对象生命周期工厂方法返回的Product实例其内部持有的Strategy引用是在构造时传入的还是在业务方法里动态set的这个时间差就是两个模式的楚河汉界。所以这篇不讲UML图不列教科书定义只拆三件事第一从JDK源码里扒出最朴素的工厂和策略实现比如Collections.sort()背后的Comparator策略和Calendar.getInstance()背后的工厂第二用一个真实到能跑通的电商折扣系统演示两者如何各司其职又无缝协作第三告诉你面试官问“区别”时真正想听的不是背诵而是你能否指出代码里那个关键的“注入时机”和“生命周期分界点”。2. 核心设计意图与本质差异从JDK源码看透边界2.1 工厂模式的本质封装对象创建过程隔离new关键字先看JDK里最不起眼却最典型的例子java.util.Calendar.getInstance()。你调用它得到一个GregorianCalendar中国默认或JapaneseImperialCalendar日本Locale下但代码里从不出现new GregorianCalendar()。这就是简单工厂的影子——它把“根据Locale选哪个日历实现类”的逻辑封在方法内部public static Calendar getInstance() { return createCalendar(TimeZone.getDefault(), Locale.getDefault()); } private static Calendar createCalendar(TimeZone zone, Locale aLocale) { if (aLocale.getLanguage() ja aLocale.getCountry() JP) { return new JapaneseImperialCalendar(zone, aLocale); } else { return new GregorianCalendar(zone, aLocale); // 这里才是真正的new } }注意这个createCalendar方法它只做一件事——根据输入参数Locale决定new哪个具体类并返回父类引用。它不关心Calendar对象创建后干什么不参与add()、get()等任何业务方法。它的责任域严格限定在“实例化”这一瞬间。再看Spring的BeanFactory更是把工厂模式推到极致XML里写bean classcom.example.PaymentService/Spring容器启动时解析配置反射调用构造器生成实例全程不让你手写new。你拿到的PaymentService对象其内部依赖的PaymentStrategy是谁工厂根本不care——那是策略模式该管的事。提示判断一段代码是否属于工厂模式就盯住一个点——所有new操作是否被集中封装在一个方法/类中且该方法只返回抽象类型接口/抽象类不暴露具体实现类名如果答案是肯定的哪怕它叫XXXUtil而不是XXXFactory它也是工厂。2.2 策略模式的本质定义算法族让它们可互相替换翻到java.util.Collections.sort(ListT, Comparator? super T)。排序算法本身快排、归并是固定的但“两个元素谁大谁小”这个规则由你传入的Comparator决定。String.CASE_INSENSITIVE_ORDER、Integer::compareTo、自定义Lambda都是不同的策略实现ListString list Arrays.asList(apple, Banana, cherry); // 策略1忽略大小写比较 Collections.sort(list, String.CASE_INSENSITIVE_ORDER); // 策略2按长度比较 Collections.sort(list, (s1, s2) - Integer.compare(s1.length(), s2.length()));关键在这里sort()方法内部对每个元素对调用的是comparator.compare(a, b)而这个comparator对象是在sort()方法被调用时作为参数传入的不是在Collections类初始化时就固定死的。这意味着同一段排序逻辑可以今天用长度策略明天换首字母策略完全不影响sort()方法本身的代码。再看JDK的java.time.format.DateTimeFormatterDateTimeFormatter.ofPattern(yyyy-MM-dd)返回一个格式化器它内部持有一个DateTimePrinterParser策略链。你调用format()时实际执行的是这个策略链的print()方法。而DateTimeFormatter.ISO_LOCAL_DATE和DateTimeFormatter.BASIC_ISO_DATE只是持有不同策略链的两个不同实例。注意策略模式的“可替换”不是靠if-else切换而是靠多态调用同一接口的不同实现。如果代码里写着if (type.equals(A)) { doA(); } else { doB(); }那只是条件分支不是策略模式——真正的策略是context.executeStrategy()而executeStrategy()方法体里只有this.strategy.doIt()这一行。2.3 二者根本性差异时间维度与职责切分把工厂和策略放在同一个时间轴上观察差异豁然开朗维度工厂模式策略模式作用时机对象创建之前编译期/启动期对象运行之中调用期/运行期核心动作new ConcreteClass()strategy.doSomething()变化原因需求变了要支持新支付方式场景变了用户选了不同优惠券修改位置工厂类内部增加if分支或新方法新增策略类不改原有代码依赖方向工厂依赖具体实现类上下文依赖策略接口不依赖实现举个生活例子做一道宫保鸡丁。工厂模式是“选哪家餐厅下单”——你打开美团搜索“宫保鸡丁”平台根据你的定位、评分、配送时间返回一家餐厅的订单入口。这个“选餐厅”过程就是工厂它决定了最终端上桌的是“眉州东坡的宫保鸡丁”还是“外婆家的宫保鸡丁”但不关心这道菜怎么做。策略模式是“这道菜的口味怎么调”——你下单时勾选“微辣”、“中辣”、“特辣”厨房师傅拿到订单后按你选的辣度策略调整花椒和辣椒的用量。同一个厨师上下文同一个菜谱框架Context类辣度策略一换成品味道就变。很多初学者混淆是因为看到“工厂里new了策略类”。比如public class DiscountContext { private DiscountStrategy strategy; public DiscountContext(String type) { this.strategy DiscountStrategyFactory.create(type); // 看似工厂用了策略 } }这里DiscountStrategyFactory.create(type)确实返回了一个策略实现但工厂本身并不执行strategy.calculate()。它只负责把策略对象造出来交给DiscountContext。DiscountContext才是策略的使用者它在calculate()方法里调用strategy.calculate()。工厂和策略的协作是“生产者”和“消费者”的关系不是“父子继承”或“主从控制”。3. 实战电商促销系统中的工厂与策略协同实现3.1 需求场景与架构设计我们来实现一个真实的电商促销引擎支持三种折扣类型满减券满300减50满500减120打折券95折、88折按原价比例计算直降券商品直降20元、50元固定金额要求新增一种折扣类型如“买一赠一”时不修改已有代码开闭原则同一订单可叠加使用多种券需策略组合不同商品类目适用不同券需工厂按类目选策略架构设计思路工厂层CouponFactory根据商品类目Category返回对应的CouponValidator验证券是否可用它只管“这个类目的商品能用哪些券”策略层DiscountStrategy接口定义calculate(OrderItem item, Coupon coupon)每个具体策略FullReductionStrategy、PercentageStrategy实现自己的计算逻辑上下文层PromotionEngine持有CouponValidator和DiscountStrategy在计算时先验证券有效性再调用策略计算折扣额这样工厂管“谁能用”策略管“怎么算”上下文管“什么时候算”三者职责清晰。3.2 工厂模式实现CouponFactory与类目驱动的验证器先定义类目枚举和验证器接口public enum Category { ELECTRONICS, CLOTHING, FOOD, BOOKS } // 验证器只负责判断这张券对这类商品是否有效 public interface CouponValidator { boolean canUse(Coupon coupon, Category category); } // 具体实现电子类商品只能用满减和直降不能用打折厂商不让利 public class ElectronicsValidator implements CouponValidator { Override public boolean canUse(Coupon coupon, Category category) { return coupon.getType() CouponType.FULL_REDUCTION || coupon.getType() CouponType.DIRECT_DEDUCTION; } } // 服装类商品所有券都可用 public class ClothingValidator implements CouponValidator { Override public boolean canUse(Coupon coupon, Category category) { return true; // 所有类型都支持 } }工厂类CouponFactory根据类目返回对应验证器public class CouponFactory { // 静态映射实际项目中可从配置中心读取 private static final MapCategory, CouponValidator VALIDATOR_MAP Map.of( Category.ELECTRONICS, new ElectronicsValidator(), Category.CLOTHING, new ClothingValidator(), Category.FOOD, new FoodValidator(), // 假设存在 Category.BOOKS, new BooksValidator() ); public static CouponValidator getValidator(Category category) { return VALIDATOR_MAP.getOrDefault(category, new DefaultValidator()); } }这里的关键设计点工厂返回的是CouponValidator接口调用方无需知道是ElectronicsValidator还是ClothingValidator新增类目如FURNITURE只需在VALIDATOR_MAP里加一行不改工厂方法逻辑验证器本身不包含任何折扣计算逻辑它只回答“能不能用”这个布尔问题实操心得工厂模式最容易犯的错是“过度设计”。曾有个团队为每种验证器都建了ElectronicsValidatorFactory、ClothingValidatorFactory结果代码量翻倍维护成本飙升。记住工厂的目的是简化创建不是制造更多工厂。一个CouponFactory统一管理所有类目的验证器比十个工厂类更符合KISS原则Keep It Simple, Stupid。3.3 策略模式实现DiscountStrategy与动态计算逻辑定义策略接口和具体实现// 策略接口给定商品项和优惠券计算能减多少钱 public interface DiscountStrategy { BigDecimal calculate(OrderItem item, Coupon coupon); } // 满减策略满300减50满500减120 public class FullReductionStrategy implements DiscountStrategy { Override public BigDecimal calculate(OrderItem item, Coupon coupon) { BigDecimal originalPrice item.getPrice().multiply(BigDecimal.valueOf(item.getQuantity())); // 解析券的门槛和减免值如300:50、500:120 String[] thresholds coupon.getRule().split(;); for (String threshold : thresholds) { String[] parts threshold.split(:); BigDecimal minAmount new BigDecimal(parts[0]); BigDecimal discount new BigDecimal(parts[1]); if (originalPrice.compareTo(minAmount) 0) { return discount; } } return BigDecimal.ZERO; } } // 打折策略95折即乘以0.95 public class PercentageStrategy implements DiscountStrategy { Override public BigDecimal calculate(OrderItem item, Coupon coupon) { BigDecimal originalPrice item.getPrice().multiply(BigDecimal.valueOf(item.getQuantity())); BigDecimal rate new BigDecimal(coupon.getRule()); // rule存0.95 return originalPrice.subtract(originalPrice.multiply(rate)); } }上下文类PromotionEngine整合工厂和策略public class PromotionEngine { private final CouponValidator validator; private DiscountStrategy strategy; // 策略在运行时注入 // 构造时通过工厂获取验证器 public PromotionEngine(Category category) { this.validator CouponFactory.getValidator(category); } // 运行时动态设置策略这才是策略模式的灵魂 public void setStrategy(DiscountStrategy strategy) { this.strategy strategy; } // 计算单个商品项的折扣 public BigDecimal calculateDiscount(OrderItem item, Coupon coupon) { // 第一步工厂提供的验证器检查券是否可用 if (!validator.canUse(coupon, item.getCategory())) { return BigDecimal.ZERO; } // 第二步策略模式计算具体折扣额 if (strategy null) { throw new IllegalStateException(Strategy not set!); } return strategy.calculate(item, coupon); } }使用示例// 创建针对电子类商品的促销引擎 PromotionEngine engine new PromotionEngine(Category.ELECTRONICS); // 为当前订单设置满减策略 engine.setStrategy(new FullReductionStrategy()); OrderItem phone new OrderItem(iPhone, new BigDecimal(6999), 1, Category.ELECTRONICS); Coupon coupon new Coupon(CouponType.FULL_REDUCTION, 300:50;500:120); BigDecimal discount engine.calculateDiscount(phone, coupon); // 返回503.4 进阶策略组合与工厂策略化真实场景中一张订单可能同时使用“满300减50”和“95折”两张券。这时需要策略组合。我们不修改PromotionEngine而是新增一个组合策略// 组合策略多个策略顺序执行 public class CompositeStrategy implements DiscountStrategy { private final ListDiscountStrategy strategies; public CompositeStrategy(ListDiscountStrategy strategies) { this.strategies strategies; } Override public BigDecimal calculate(OrderItem item, Coupon coupon) { BigDecimal totalDiscount BigDecimal.ZERO; BigDecimal currentPrice item.getPrice().multiply(BigDecimal.valueOf(item.getQuantity())); for (DiscountStrategy strategy : strategies) { // 每个策略基于当前价格计算折扣如先满减再打折 BigDecimal discount strategy.calculate(new OrderItem( item.getName(), currentPrice, item.getQuantity(), item.getCategory()), coupon); totalDiscount totalDiscount.add(discount); currentPrice currentPrice.subtract(discount); } return totalDiscount; } }此时工厂模式还能发挥作用——我们可以让工厂根据券的组合规则返回预配置的组合策略public class StrategyFactory { public static DiscountStrategy getStrategy(ListCoupon coupons) { if (coupons.size() 1) { return getSingleStrategy(coupons.get(0)); } else { // 检查是否为满减打折组合 boolean hasFullReduction coupons.stream() .anyMatch(c - c.getType() CouponType.FULL_REDUCTION); boolean hasPercentage coupons.stream() .anyMatch(c - c.getType() CouponType.PERCENTAGE); if (hasFullReduction hasPercentage) { return new CompositeStrategy(Arrays.asList( new FullReductionStrategy(), new PercentageStrategy() )); } } throw new UnsupportedOperationException(Unsupported coupon combination); } }这样PromotionEngine的使用变得更简洁// 自动根据券列表选择最优策略 DiscountStrategy autoStrategy StrategyFactory.getStrategy(order.getCoupons()); engine.setStrategy(autoStrategy);注意事项策略组合不是简单相加。比如“满300减50”和“95折”到底是先减50再打95折还是先打95折再减50这涉及商业规则必须在组合策略的calculate()方法里明确顺序。我在某电商项目踩过的坑是财务同事说“必须先满减后打折”结果开发按“先打折后满减”上线导致首月多返了17万券回滚时发现数据库里已生成20万条错误订单记录。教训是策略的执行顺序必须作为策略实现的一部分固化不能靠调用方临时拼接。4. 常见误区与避坑指南从面试现场到线上事故4.1 误区一“工厂模式就是if-else策略模式就是switch”这是最普遍的认知陷阱。看这段代码public class PaymentService { public void pay(String type, BigDecimal amount) { if (alipay.equals(type)) { new Alipay().pay(amount); } else if (wechat.equals(type)) { new WechatPay().pay(amount); } else if (unionpay.equals(type)) { new UnionPay().pay(amount); } } }很多人说这是“简单工厂”但严格来说它只是条件分支不是工厂模式。因为它没有返回统一的抽象类型如PaymentProcessor接口而是直接调用具体类方法调用方pay()方法仍需知道Alipay、WechatPay这些具体类名新增支付方式要改pay()方法违反开闭原则真正的工厂模式改造public interface PaymentProcessor { void pay(BigDecimal amount); } public class Alipay implements PaymentProcessor { ... } public class WechatPay implements PaymentProcessor { ... } public class PaymentFactory { public static PaymentProcessor getProcessor(String type) { switch (type) { case alipay: return new Alipay(); case wechat: return new WechatPay(); default: throw new IllegalArgumentException(Unknown type: type); } } } // 使用方 PaymentProcessor processor PaymentFactory.getProcessor(alipay); processor.pay(amount); // 多态调用不依赖具体类实操心得判断是否是工厂模式就看调用方代码里有没有出现new XXX()。如果有说明工厂没起到隔离作用如果调用方只写factory.create()和接口方法那才是合格的工厂。4.2 误区二“策略模式必须用接口不能用抽象类”不少教材强调“策略要用接口”导致新人一看到抽象类就否定。其实JDK自己就打了脸java.util.AbstractMap是抽象类但它定义了put()、get()等模板方法子类只需实现entrySet()这就是模板方法模式——而模板方法和策略模式常一起用。再看Spring的JdbcOperations它是接口但JdbcTemplate是它的实现类内部大量使用策略模式如PreparedStatementCallback、RowMapper而这些回调接口完全可以是抽象类// 抽象策略类提供通用逻辑 public abstract class DiscountCalculator { protected BigDecimal basePrice; public DiscountCalculator(BigDecimal basePrice) { this.basePrice basePrice; } // 模板方法定义算法骨架 public final BigDecimal calculate() { BigDecimal discount doCalculate(); // 子类实现 return basePrice.subtract(discount).max(BigDecimal.ZERO); } // 钩子方法由子类决定 protected abstract BigDecimal doCalculate(); } // 具体策略继承抽象类 public class SeasonalDiscount extends DiscountCalculator { public SeasonalDiscount(BigDecimal basePrice) { super(basePrice); } Override protected BigDecimal doCalculate() { return basePrice.multiply(new BigDecimal(0.2)); // 2折 } }用抽象类的好处可以复用公共字段如basePrice、公共方法如validate()校验、甚至默认实现如doCalculate()返回0。接口只能定义行为契约不能提供状态和实现。4.3 误区三“工厂和策略必须分开不能在一个类里”有些架构师强行规定“工厂类只能有create方法策略类只能有execute方法”结果写出一堆孤岛类。真实项目中工厂可以返回策略策略可以持有工厂只要职责清晰。例如// 策略类内部需要根据商品属性动态选择子策略 public class DynamicPricingStrategy implements DiscountStrategy { private final ProductCategoryFactory categoryFactory; // 策略持有工厂 public DynamicPricingStrategy(ProductCategoryFactory factory) { this.categoryFactory factory; } Override public BigDecimal calculate(OrderItem item, Coupon coupon) { // 根据商品类目用工厂获取对应的定价策略 PricingStrategy pricingStrategy categoryFactory.getStrategy(item.getCategory()); return pricingStrategy.calculate(item.getPrice()); } }这里DynamicPricingStrategy是策略它内部使用ProductCategoryFactory工厂来获取更细粒度的策略。这不是混乱而是策略模式的嵌套使用——外层策略决定“用哪个工厂”内层工厂决定“用哪个具体策略”。就像汽车导航外层策略是“选路线模式最快/最省油/避开收费”内层工厂根据当前路段实时选择“跟车距离策略近/中/远”。4.4 线上事故复盘一次因混淆导致的资损去年双11某电商平台的优惠券系统出现大规模资损。根因是开发将策略模式的“运行时注入”误写成工厂模式的“启动时创建”。原始代码Component public class CouponService { private final DiscountStrategy strategy; // Spring注入启动时就确定 public CouponService(DiscountStrategy strategy) { this.strategy strategy; // 问题在这里 } public BigDecimal calculate(Order order) { return strategy.calculate(order.getItem(), order.getCoupon()); } }配置类里这么写Bean ConditionalOnProperty(name coupon.strategy, havingValue full-reduction) public DiscountStrategy fullReductionStrategy() { return new FullReductionStrategy(); }问题在于ConditionalOnProperty是启动时读取配置一旦应用启动strategy就固定为FullReductionStrategy无法在运行时根据用户选择的券类型切换。而实际需求是用户下单时前端传couponTypepercentage后端应动态使用PercentageStrategy。修复方案Service public class CouponService { // 工厂注入而非策略注入 private final StrategyFactory strategyFactory; public CouponService(StrategyFactory factory) { this.strategyFactory factory; } public BigDecimal calculate(Order order) { // 运行时根据order里的couponType决定用哪个策略 DiscountStrategy strategy strategyFactory.getStrategy(order.getCoupon().getType()); return strategy.calculate(order.getItem(), order.getCoupon()); } }这次事故损失约83万元教训深刻工厂模式的决策点在应用生命周期早期启动、初始化策略模式的决策点在业务方法调用时请求处理中。把后者提前到前者就失去了策略模式存在的意义。5. 如何选择什么场景用工厂什么场景用策略什么场景两者合用5.1 单独使用工厂模式的典型场景当你遇到以下情况优先考虑工厂模式对象创建逻辑复杂需要读配置、连DB、调RPC才能确定用哪个实现类例日志框架中根据logback.xml的appender标签创建ConsoleAppender或FileAppender隐藏具体类名对外提供统一API内部实现可能随时更换例javax.crypto.Cipher.getInstance(AES)JDK可自由切换BouncyCastle或SunJCE实现统一管理对象生命周期需要单例、原型、线程局部等作用域控制例Spring的Scope(prototype)Bean每次getBean()都调用工厂创建新实例判断口诀“new操作分散在各处导致修改困难” → 用工厂集中创建。5.2 单独使用策略模式的典型场景当你遇到以下情况策略模式是首选算法需要动态切换同一功能不同用户/场景/配置下行为不同例风控系统中“用户信用分计算”策略新用户用注册信息算老用户用行为数据算避免条件分支爆炸if-else超过5个分支且每个分支逻辑复杂例消息推送渠道选择iOS用APNsAndroid用FCM鸿蒙用PushKitWeb用WebSocket算法需可测试、可替换单元测试时用Mock策略隔离外部依赖例支付回调验签用MockSignatureStrategy返回true/false不调真实验签服务判断口诀“同一段业务逻辑因输入不同而执行不同算法” → 用策略封装算法差异。5.3 工厂策略协同的黄金组合场景当系统同时满足以下条件工厂与策略必须联手创建对象时需根据环境选型创建后还需根据运行时数据选行为例上面的电商促销系统——工厂按商品类目选验证器环境策略按券类型选计算逻辑运行时策略实现类本身需要复杂初始化如连接池、缓存、配置加载例RedisCacheStrategy需初始化JedisPoolCaffeineCacheStrategy需配置maximumSize工厂可封装这些初始化逻辑策略需按租户隔离不同客户用不同策略实现且策略配置存储在DB中例SaaS系统中客户A用规则引擎策略客户B用机器学习策略工厂根据tenant_id从DB加载配置并创建对应策略此时的协作范式是工厂负责“造容器”策略负责“填内容”。容器如PromotionEngine持有策略接口引用工厂在容器创建时注入其依赖如验证器而策略本身在业务方法中动态设置或由策略工厂提供。最后分享一个小技巧在IDEA里给策略接口打个FunctionalInterface注解然后用Lambda写策略代码量锐减。比如DiscountStrategy可以直接这样用engine.setStrategy((item, coupon) - { BigDecimal price item.getPrice().multiply(BigDecimal.valueOf(item.getQuantity())); return price.multiply(new BigDecimal(0.1)); // 10% off });但要注意Lambda策略无法被Spring管理不能注入其他Bean适合简单逻辑复杂策略需DB、缓存、事务仍需写成类。我在实际项目中发现真正让团队效率提升的不是死记硬背23种模式而是建立一套快速判断的肌肉记忆看到new关键字满天飞就想到工厂看到if-else嵌套三层以上就想到策略看到“既要又要”既要按环境创建又要按数据切换那就工厂策略一起上。模式是工具不是枷锁——用得顺手就是好模式。