1. 从一个失控的if-else说起工厂模式到底止住了什么1.1 那段让我决心重写的代码几年前我参与开发一个订单收银系统最开始支付通道只有银行卡和余额两种代码写得很直接if (CARD.equals(payType)) { return cardPayService.pay(request); } else if (BALANCE.equals(payType)) { return balancePayService.pay(request); } else { throw new UnsupportedPayTypeException(payType); }当时觉得特别清爽一眼看懂。直到需求方提出要接入“积分抵扣“我在这段if-else里又加了一个分支。后来没过两个月又要支持“组合支付“我发现情况不对劲这段逻辑散落在订单服务、退款服务、对账服务好几个地方每个地方都有类似的判断测试用例也跟着翻倍。最离谱的是有一次只是调整支付处理顺序结果把整个下单链路都影响到了。真正让我下决心重构的是一次联调事故新增渠道时我在订单服务里加了分支却漏掉了退款服务的分支结果用户用新渠道支付后无法原路退回。问题暴露后我看到那段又臭又长的if-else意识到问题不是“分支多“而是创建对象的决策点压根没有收敛。当时团队里一位老工程师建议我看看工厂模式这一看就是好几年的实践和踩坑。1.2 解耦的关键不是消灭条件判断而是调整依赖方向很多人对“解耦“有误解以为解耦等于代码里没有if、没有switch。实际上条件判断永远存在问题只在于它放在哪里、由谁来做。还是拿支付场景说。订单服务里那段if-else的问题不在于“根据payType选择不同实现“这个动作本身而在于订单服务直接依赖了所有支付实现类。它必须知道CardPayService存在、BalancePayService存在、积分PayService存在才能写出完整的分支。这就产生了一个强烈的耦合新增一个支付方式就要修改订单服务这个调用方。工厂模式做的事情是在调用方和具体产品类之间插入一个中间层// 调用方不再关心有哪些实现类 PaymentProcessor processor paymentProcessorFactory.get(payType); PayResult result processor.process(request);调用方只依赖工厂和产品接口不再依赖具体实现。新增支付渠道时订单服务不需要改动需要改的是工厂的注册逻辑——理想情况下连工厂都不用改只需要新增一个实现类并完成注册。这背后的本质是依赖方向的调整。调整之前调用方“向上“依赖具体产品调整之后调用方和产品都“横向“依赖抽象接口具体的创建决策被放进工厂。这才是工厂模式真正值钱的地方它不消灭复杂性而是把复杂性搬到更合适的位置让业务代码的演进不再牵一发动全身。1.3 三个名字三类思路先建立准确的心智模型学习工厂模式最让人头大的是它有三个名字简单工厂、工厂方法、抽象工厂。很多人把它们当成三种难度递增的实现方式其实它们解决的问题各有侧重点。必须先建立一个准确的心智模型简单工厂本质是一个集中创建入口把散落的new操作收敛到一个方法里。它严格来说不是GoF提出的设计模式更像一种编码习惯。工厂方法把“创建对象”这个动作抽象到接口里由子类决定具体创建哪个产品强调“扩展点”。抽象工厂处理的不再是单个产品而是产品族。多个产品之间有配套约束时用抽象工厂保证它们能成套创建。这个区别在后面会展开。你要是只记住一句话请记住简单工厂收敛创建逻辑工厂方法提供扩展点抽象工厂约束产品组合。带着这个认知往下看代码思路会清晰很多。2. 三种工厂模式不是同一层东西拆开看简单工厂、工厂方法与抽象工厂2.1 简单工厂一个集中收敛的创建入口简单工厂最常见的写法是定义一个静态方法根据参数返回不同产品对象public class PaymentProcessorFactory { public static PaymentProcessor create(String payType) { return switch (payType) { case CARD - new CardProcessor(); case BALANCE - new BalanceProcessor(); case POINTS - new PointsProcessor(); default - throw new IllegalArgumentException(不支持的支付类型: payType); }; } }我第一轮重构引入的就是这个。好处非常直接订单服务、退款服务、对账服务里的if-else全都没了大家都统一走PaymentProcessorFactory.create(payType)。新增支付渠道时只改这一个类。但它的问题也很明显每次新增渠道都要回过头来修改这个工厂类。如果你的支付类型只有三五种这种写法完全够用可一旦超过十个或者产品实现类的构造参数开始需要从配置中心读取、需要额外组装依赖时这个工厂会变得越来越臃肿。它之所以被很多人不当作“标准工厂模式”正是因为它违背了开闭原则——每次扩展都意味着修改已有代码。但注意我不是说它不好很多项目用简单工厂绰绰有余关键是你要知道自己用的是哪种。2.2 工厂方法让子类决定实例化谁工厂方法的核心是把创建对象这件事情定义为抽象方法由子类去实现。订单系统可以改成这样每种支付渠道有自己对应的工厂实现而调用方只依赖抽象工厂接口。public interface PaymentProcessorFactory { String type(); PaymentProcessor create(); } public class CardProcessorFactory implements PaymentProcessorFactory { Override public String type() { return CARD; } Override public PaymentProcessor create() { return new CardProcessor(); } }看到区别了吗创建CardProcessor的逻辑被移到了CardProcessorFactory里而不是集中在一个大工厂的switch里。当新增一种支付渠道时你不需要修改已有的工厂类只需要再写一个对应的Factory实现即可。这就是“对扩展开放对修改关闭”在创建环节的落地。工厂方法之所以强大还在于它常常和“模板方法”配合出现。比如一个文件导入框架定义好读取流程把解析器创建交给子类public abstract class ReportImporter { public void importData(String filePath) { ReportParser parser createParser(); // 工厂方法 ListString lines readFile(filePath); Report report parser.parse(lines); save(report); } protected abstract ReportParser createParser(); }CsvImporter只需要实现createParser返回CsvParserJsonImporter返回JsonParser。流程骨架被复用创建细节被推迟到子类。这种用法在实际框架代码里非常多。2.3 抽象工厂当产品之间绑定时产品族才是真正变化点抽象工厂解决的问题要再往上拉一层。假设你要做一套桌面应用的UI组件需要支持浅色主题和深色主题。主题下面还有按钮、复选框、输入框等多个组件。如果只是创建单个对象简单工厂就够了但这里存在一个约束浅色主题下按钮、复选框、输入框必须都是浅色风格不能出现按钮是浅色、复选框却是深色的混搭。这时候就要上抽象工厂public interface UiFactory { Button createButton(); Checkbox createCheckbox(); Input createInput(); } public class LightUiFactory implements UiFactory { Override public Button createButton() { return new LightButton(); } Override public Checkbox createCheckbox() { return new LightCheckbox(); } Override public Input createInput() { return new LightInput(); } } public class DarkUiFactory implements UiFactory { // 对应创建深色组件 }调用方只依赖UiFactory接口具体是浅色还是深色在启动阶段一次性指定。之后所有组件都是从该工厂创建自然就保证了成套一致性。注意抽象工厂的关键不是“一个工厂能创建多个对象”而是**“多个产品之间存在配套关系必须一起变化”**。如果你只是需要创建多种独立产品抽象工厂反而会带来不必要的耦合。2.4 三种形态的对比选型表维度简单工厂工厂方法抽象工厂核心意图收敛创建逻辑提供创建扩展点约束产品族组合变化点产品类型变化单个产品的创建方式一整套产品的风格/组合创建对象数量单个单个多个且需配套扩展方式修改工厂方法新增子类实现新增整套工厂实现违反开闭原则是基于switch/if否否典型应用类型不多的组件装配框架流程中对象的创建跨平台UI、主题切换我的经验是普通业务项目里用到最多的其实是简单工厂和带注册表的工厂工厂方法在框架设计里更常见抽象工厂则相对小众——不是不重要而是大部分业务场景根本达不到“产品族强制配套”的复杂度。别硬上。3. 支付渠道与文件解析器两个真实场景里的工厂化改造记录3.1 场景一支付渠道处理器的工厂化改造我参与的项目后来还是踩过很多坑。第二版重构时支付渠道数量到了七八种简单工厂已经非常臃肿。我们做了两个关键改造。第一把所有处理器统一成接口public interface PaymentProcessor { String type(); void process(PaymentRequest request); }第二工厂不再用switch逐一把实现类new出来而是接收一个处理器列表构建type到处理器的映射Component public class PaymentProcessorFactory { private final MapString, PaymentProcessor processorMap; public PaymentProcessorFactory(ListPaymentProcessor processors) { processorMap processors.stream() .collect(Collectors.toMap(PaymentProcessor::type, p - p)); } public PaymentProcessor get(String type) { PaymentProcessor processor processorMap.get(type); if (processor null) { throw new IllegalArgumentException(不支持的支付类型: type); } return processor; } }依赖注入容器会把所有PaymentProcessor实现类自动注入到这个List里工厂本身不再承担“判断该创建谁”的职责。新增支付渠道时我们只需要写一个新的PaymentProcessor实现类业务代码和工厂类一行都不用改。这次改造带来的效率提升非常明显。最直接的变化是新增支付方式的开发任务从“改一个大方法联动改退款/对账/报表”缩小到“新增一个类补充测试”。团队可以并行开发多个支付渠道的适配几乎不会出现代码冲突。3.2 场景二文件导入模块用工厂方法替代父类重写另一个项目是通用文件导入模块要支持CSV、JSON、Excel三种格式。第一版代码我写得很草率在解析入口处放了一个巨大的分支if (format.equals(CSV)) { ... } else if (format.equals(JSON)) { ... } else if (format.equals(XLSX)) { ... }每次新增格式入口逻辑就膨胀一点而且新增格式的同事经常看不懂原来的解析流程。后来我按工厂方法重构了这块。核心思路是抽象出配置导入器public interface ImportHandler { String supportedFormat(); void importData(InputStream input); }CSV、JSON、Excel各自实现ImportHandler每个Handler自己负责创建对应的解析器、读取数据、做校验。业务侧想要导入文件只需要根据文件后缀找到对应的Handlerpublic class ImportService { private final MapString, ImportHandler handlerMap; public void importFile(String fileName, InputStream input) { String ext FileUtil.getExtension(fileName); ImportHandler handler handlerMap.get(ext); if (handler null) { throw new UnsupportedFileFormatException(ext); } handler.importData(input); } }这样改动之后新增一种文件格式就是在实现类里完整定义自己的解析和校验逻辑不影响其他格式。测试也可以单独对一个格式的Handler做完整覆盖不需要再把所有格式的解析用例全跑一遍。3.3 从改造中总结的“效率红利”到底在哪经历这两个改造后我对工厂模式所谓“高效”有了更具体的认知。它带来的效率不是代码行数减少而是下面这几项改动影响面变小。新增实现时既有代码基本不动回归测试范围显著收窄。并行协作更顺畅。不同实现在不同类里多人同时开发不抢同一个文件。业务逻辑变干净。调用方不再被创建细节和类型判断淹没读代码时注意力集中在真正的业务流程上。扩展入口明确。新同事接手时看到工厂就能知道“接入新类型该去哪里”不用在几万行代码里大海捞针。当然代价也有类数量变多间接层变厚。但当一个项目已经出现多处重复的创建分支时这点代价是值得的。4. 工厂模式提升效率的底层逻辑开闭原则、延迟创建与依赖方向4.1 开闭原则在工厂这里落地的两种方式开闭原则说“对扩展开放对修改关闭”很多人把它理解成教条但放在工厂模式里它是很具体的工程收益。简单工厂为什么不完全符合开闭原则因为它每次新增产品都要改那个switch。工厂方法为什么符合因为新增产品只需新增一个Factory子类已有的Factory不用动。抽象工厂同样如此新增一套风格只需新增一个工厂实现。这两种方式本质上是“修改已有的代码”和“新增代码”之间的选择。短期看改旧代码可能更快但长期看旧代码被反复修改的风险远高于新增独立类。我在实际项目里见过不少因为反复修改旧工厂而导致回归问题的情况尤其是那些“顺手加一个分支”的改动很容易在不知不觉中破坏已有逻辑。所以我的判断标准是如果这个创建入口预计会频繁变化优先考虑支持扩展的工厂方法如果类型非常稳定简单工厂就够了。4.2 依赖倒置把“具体依赖”扭转为“抽象依赖”依赖倒置原则听起来很绕其实和工厂模式强相关。高层模块不应该依赖低层模块它们都应该依赖抽象。我常用一个点餐类比来说清楚这件事顾客调用方不需要知道后厨哪道菜是哪个厨师做的他只需要对着出餐口工厂下单传参数然后拿到菜产品。出餐口屏蔽了后厨内部的复杂性顾客和后厨都能独立变化只要出餐口的规则不变。从依赖图来看没有工厂时是“调用方 - 具体实现A/B/C”有工厂后变成“调用方 - 工厂接口 - 具体工厂实现 - 产品接口 - 具体产品”。箭头方向从“指向具体类”变为“指向抽象”这就是依赖方向被扭转过来了。依赖倒置让业务代码不再与具体的第三方实现绑定。比如某个支付通道的SDK升级了老接口废弃了你只需要在对应的Processor实现里改掉适配逻辑业务入口完全不受影响。4.3 延迟实例化与生命周期控制工厂比new多做的那些事如果工厂模式只是包一层new那价值确实有限。但工厂还能解决“什么时候创建”和“创建多少个”的问题这是直接new做不到的。场景很常见一个云存储客户端初始化时要去拉取配置、建立连接池非常重。如果系统启动时就创建好浪费资源如果每次调用都创建性能扛不住。这时候工厂可以做成懒加载单例public class StorageClientFactory { private volatile StorageClient client; public StorageClient getClient() { if (client null) { synchronized (this) { if (client null) { client createClient(); } } } return client; } private StorageClient createClient() { // 初始化连接池、读取配置等重量级操作 return new StorageClient(config, pool); } }这就是双重检查锁的懒加载单例。工厂把“创建策略”封装在内部调用方只要调用getClient就行。类似地工厂内部也能做池化、按租户隔离、多实例路由等生命周期控制这些逻辑如果散落在业务代码里很快就乱套了。4.4 参数驱动和配置驱动工厂走向自动化的两种形态很多系统的产品类型多到不可能用if列举比如插件式架构。这时候“创建什么”这件事可以由配置项、类型名甚至运行时发现机制来决定。在Java生态里类似ServiceLoader的机制可以自动发现实现类很多依赖注入容器也支持把某个接口的所有实现自动装配成List或Map。我改造支付工厂时用的就是容器自动注入List的方式。在Go语言里常见的做法是各实现包在init函数里向一个全局注册表注册自己工厂查询注册表来创建对象。不管底层机制是什么它们的本质都从“手工写分支”升级成了“按注册信息自动分派”。工厂类退化为一个查询入口真正的扩展能力在注册机制里。对大多数团队来说能利用框架已有的这种能力就不要自己再造轮子否则你维护的就不只是业务代码还有一套自研的插件系统。5. 工厂模式不是银弹常见反模式、踩坑与排查经验5.1 反模式一工厂类渐渐变成“上帝类”引入工厂后最常见的坏味道是工厂类从一个简单的创建入口慢慢膨胀成什么都管的“上帝类”。症状很典型create方法里除了switch还要做参数解析、配置校验、权限判断、日志埋点。功能越加越多工厂变成了一团黏糊糊的胶水。我见过一些项目的工厂方法有几百行产品类型判断、分支处理、异常兜底全在里面。这个问题的本质是没有真正拆解变化。解决思路有两个方向一是把创建逻辑拆到各产品自己的工厂里大工厂只做分派二是当创建过程本身很复杂时引入Builder模式来组装复杂产品对象。反模式要警惕的不是工厂而是借工厂之名把复杂度集中到一个类里。5.2 反模式二类型判断在业务代码里重复出现另一种隐蔽的反模式是明明用了工厂业务代码里却还在到处判断具体类型。比如从工厂拿到的对象接着又写if (processor instanceof CardProcessor)。出现这种代码往往意味着产品接口设计得不够好一些差异性的行为没有被抽象进接口。调用方不得不去识别具体类型才能处理特例。正确的方向是尽量把变化行为收敛到产品接口内部让调用方只调用接口方法。如果确实存在偶发的特例可以尝试在接口里增加带默认实现的方法。最忌讳的是把instanceof当作万能钥匙在十个地方散落着对具体类型的判断这样工厂只是走了一个形式耦合并没有真正消失。5.3 反模式三硬把不同维度的产品塞进一个抽象工厂我在另一个项目里见过有人把“支付渠道”和“风控策略”放进同一个抽象工厂理由是“支付时总要同时做风控”。表面看确实都在一次请求里出现但这两个东西的变化频率和生命周期完全不同支付渠道可能每个月都要新增风控策略可能一年调整一次。硬把它们绑在一个产品族里导致新增一个支付渠道时要改抽象工厂接口所有既有实现都要跟着改一遍。这是抽象工厂最典型的误用。抽象工厂应当只服务于“产品之间有强配套约束”的场景比如主题风格、操作系统界面、数据库方言。不同维度的业务对象即使经常一起出现也不等于它们需要成为一个产品族。遇到这种情况拆成两个独立工厂远好于强行组合。5.4 一次线上问题复盘工厂缓存导致规则不生效讲一个具体的踩坑经历。某个促销引擎里规则解析器采用工厂模式根据规则类型从工厂获取对应的解析器工厂内部对解析器做了缓存避免每次重复创建。上线后收到反馈改了某个优惠规则参数控制台保存成功但实际计算还是旧结果。初步排查时大家以为是缓存中间件的问题后来沿着调用链查才发现问题出在规则解析器工厂上。工厂缓存的是一个解析器实例而解析器内部持有规则定义对象的引用。配置中心推送新规则后系统更新了规则库但工厂缓存的解析器还是旧对象自然算不出新结果。修复方案不复杂把缓存策略改成版本感知解析器的key里带上规则版本号规则变更时重建解析器。这个案例给我的教训是工厂模式一旦接管对象的生命周期就必须同步考虑“对象依赖的数据变了之后工厂要不要重建它”。懒加载、缓存、池化这些策略都不是免费的它们都有各自的失效时机和一致性要求。5.5 线程安全与并发创建容易被忽略的隐性坑另一类隐蔽问题是多线程环境下的工厂创建。如果工厂内部用了一个普通的HashMap做缓存并发调用时可能出现两个线程同时创建两个实例破坏单例语义或者HashMap扩容时产生并发问题。建议是缓存Map使用ConcurrentHashMap需要懒加载时用computeIfAbsent这类原子方法如果需要严格单例用双重检查锁加volatile。如果项目本身使用了依赖注入容器来管理Bean这些并发问题容器都帮你处理好了尽量不要自己在工厂里再维护一套单例逻辑除非你的对象确实不能在容器里管理。6. 选型判断什么时候上工厂什么时候直接“new”6.1 直接构造对象即可的场景别为模式而模式设计模式不是越用越好工厂也一样。如果一个对象的构造函数很干净产品类型基本不会变化调用方也不需要隐藏实现细节那直接new是最诚实、最清晰的写法。比如一个纯工具类对象new Mapper()本身就足够表达意图再包一层工厂只会让阅读者多绕一道弯。我见过有人连一个DTO的创建都非要套工厂最后工厂方法里只有一行return new XxxDTO(args)这种就是典型的形式主义。判断标准很简单直接new是否已经带来实际的维护负担。没有负担就不要引入间接层。6.2 值得引入工厂的信号清单反过来出现以下任一信号就该考虑用工厂创建逻辑复杂参数解析、依赖组装、环境判断掺杂在一起同一接口有多个实现且调用方需要在运行时确定具体类型需要控制对象生命周期比如懒加载、单例、池化希望隐藏实现类调用方只依赖抽象接口新增产品时希望扩展点是“新增类”而不是“修改旧类”。如果一条都不满足直接new。如果满足一条但场景很轻先试简单工厂随着变化点增多再平滑演进到注册表或工厂方法。不要一上来就上最重的方案。6.3 现代语言与框架里的工厂替代品工厂模式在现代语言里有很多更轻量的表达方式不一定非要建一个XxxFactory类。依赖注入容器本身就是自动化的工厂。你把一个接口的实现注册进容器业务代码里直接声明接口依赖容器负责在运行时创建和注入实例。这种情况下再手写一个工厂往往是多余的。函数式接口也能当工厂用。定义SupplierT或FunctionString, T创建逻辑就是一个Lambda比写完整的工厂类轻得多。Python里用字典映射类型名到构造函数也很常见。Go里注册表加工厂函数是主流做法。这些替代方案的共同点都是把“如何创建”和“何时创建”从调用方剥离只是语法形态不同。理解这一点你就不会拘泥于某个固定的类名结构而是真正掌握了模式背后的思想。6.4 一点实战建议先简单后复杂如果让我给团队定一条关于工厂模式的简单约定我会写这几条创建逻辑不超过几行、类型变化很少直接用构造器有两个以上产品变体且调用方不能确定具体实现时用工厂工厂类统一命名像XxxFactory或XxxRegistry放在依赖注入边界附近不要散落在业务包深处新同事接项目时先去看每个工厂的文档注释弄清“type/key怎么映射到实现类”扩展才不会找错地方。记住一点设计模式的最终目的是让项目在持续演进的过程中每次调整需求时花费更少的时间和精力。工厂模式如果应用得当它让“新增一种类型”变成一件可以局部完成、反复执行也不心疼的事情应用过度它只是把一个简单的new变得复杂。没有哪种模式比另一种更高明只有“用到合适的位置”和“放错了位置”的区别。