
我知道你迟早会碰见工厂模式这个名字。不管你是写 Java、Python、Go还是前端写 TypeScript只要你做过稍微大一点的项目一定见过Factory、FactoryMethod、AbstractFactory这种类名或目录名。很多人第一次看到它内心只有一个想法搞这么绕干嘛直接new一个对象不就行了我刚开始接触设计模式时也是这个反应。直到后来在真实项目中被“到处new、改一处崩三处”折磨过几轮才明白工厂模式到底在解什么问题。这篇我把自己的理解从头到尾捋一遍不堆概念不抄书尽量用实际业务场景把三种工厂讲透——简单工厂、工厂方法、抽象工厂分别解决什么问题什么时候用哪一种每种都有可直接跑的代码示例再附上我会踩的坑和排查思路。看完你要是还不会那我真吃。1. 工厂模式到底在解决什么从“到处 new”到“解耦”先说一个特别真实的问题。假设你在做一个电商系统订单生成后要根据不同的支付方式处理后续逻辑微信支付、支付宝、银行卡、还有礼品卡抵扣。如果第一版你图快直接在业务代码里写if (payType.equals(WECHAT)) { PayService wechat new WechatPayService(); wechat.pay(order); } else if (payType.equals(ALIPAY)) { PayService alipay new AlipayPayService(); alipay.pay(order); }看着没毛病甚至可以跑得很好。但问题藏在两个地方第一创建对象的逻辑和业务逻辑混在一起。你的下单接口本来应该专注“处理订单流程”结果里面全是支付渠道的实例化细节。第二每加一个支付渠道你就要修改这段 if-else。今天加个云闪付明天加个数字人民币钱包每次上线都要动这坨代码而且改动的地方可能不止一处——下单用一次退款又要判断一次对账再判断一次。工厂模式解决的就是这两个痛点把“创建对象”这一步从业务代码里抽出来用统一的入口管理。业务方只关心“我要哪种支付方式”不关心“这个支付对象是怎么 new 出来的构造参数是啥底层走了哪些初始化”。这就是所谓的“创建与使用分离”。打个比方。你去餐厅吃饭不会自己跑进厨房和面、切菜、炒菜你只需要告诉服务员“来一份宫保鸡丁”。至于这道菜是哪个厨师做的、用了哪个牌子的酱油、开多大火你完全不用知道也不会因为你多坐了一会儿就得重写点菜逻辑。厨房里的东西变了菜单不变你照样点菜。工厂模式就是那个“服务员 后厨”之间的调度机制。从代码层面看好处就三条调用方干净了新增实现时改动最小化对象创建逻辑统一收敛。其中第二条最重要——如果你能做到“加新类型时只新增一个类不改动已有代码”那系统就具备了良好的扩展性这也是设计模式最核心的追求。但这里要泼一盆冷水工厂模式不是银弹。如果一个系统里对象类型长期稳定、永远不会增加硬套工厂反而增加一层理解成本。先判断“变”与“不变”再决定要不要用工厂。“可能以后会增加类型”和“已经在频繁增加类型”是两种完全不同的决策前提前者用简单判断就够了后者才值得引入工厂。2. 三种工厂形态拆解一个比一个抽象一个比一个灵活很多教程把工厂模式直接分成三种来讲但很少有人告诉你这三种之间到底是什么关系。我的理解是它们是同一个问题在不同约束条件下的三种解法复杂度逐级递增。先看一张速查对比表后面逐个展开。形态核心思路类型新增方式需要改动的已有代码适合场景简单工厂一个工厂类用条件判断返回不同对象修改工厂类工厂类内部类型少、变化不频繁工厂方法每个产品对应一个工厂子类新增子类基本不改类型多且经常扩展抽象工厂一组工厂子类生产一组相关产品新增一组工厂子类基本不改系统级产品族概念展开讲之前必须强调一点简单工厂严格来说不是 GoF 二十三种设计模式之一它只是工厂模式家族中使用频率最高、也最好理解的一种“编程习惯”。但它确实好用而且工程上大量存在所以大家把它和工厂方法、抽象工厂放在一起讲。你不用纠结学术定义理解它在什么场景下用就行。2.1 简单工厂一个类搞定所有创建逻辑简单工厂的思路特别直白定义一个专门的工厂类根据传入的参数用条件判断或 switch 返回不同类型的对象。代码长这样public class PayServiceFactory { public static PayService create(String payType) { if (WECHAT.equals(payType)) { return new WechatPayService(); } if (ALIPAY.equals(payType)) { return new AlipayPayService(); } if (CARD.equals(payType)) { return new CardPayService(); } throw new IllegalArgumentException(unknown pay type: payType); } }调用方原来的 if-else 全部替换成一行PayService payService PayServiceFactory.create(payType); payService.pay(order);好处立刻体现业务代码里再也没有new WechatPayService()这种字样了以后就算 WechatPayService 构造方法加了参数、换了底层 SDK你也只需要改工厂内部调用方完全感知不到变化。这层“隔离”就是价值。但注意简单工厂没有解决 if-else 本身的问题只是把 if-else 挪了个窝。从业务代码挪到了工厂类里整体可维护性提升了但工厂类内部依然是随着类型增加而不断膨胀的。所以它最适合的场景是产品类型少、新增频率低、同一体系下的对象创建逻辑统一管理。写简单工厂时有三个小细节值得注意。第一工厂方法一般用static因为工厂类不需要维护状态直接用静态方法调用最方便第二返回类型尽量声明为父类或接口这样调用方拿到的是抽象而不是具体实现类第三异常处理必须有。传入一个不存在的类型时必须明确抛出异常而不是返回 null。返回 null 会让问题潜伏到调用处排查起来非常痛苦直接抛出IllegalArgumentException就能第一时间暴露问题。2.2 工厂方法每种产品配一个工厂扩展时不动旧代码简单工厂的不足在类型变得频繁时会被放大。假设每半年要接一种新的支付渠道每次都要打开工厂类加一个分支工厂类很快变成几百行的“上帝类”。工厂方法模式就是冲着这个问题来的。核心思想把创建逻辑下沉到子类。定义一个抽象的工厂接口每种产品对应一个具体的工厂子类每个子类只负责生产一种产品。public interface PayServiceFactory { PayService create(); }public class WechatPayServiceFactory implements PayServiceFactory { Override public PayService create() { return new WechatPayService(); } }public class AlipayPayServiceFactory implements PayServiceFactory { Override public PayService create() { return new AlipayPayService(); } }调用方怎么用它不再直接依赖工厂类而是先拿到对应的工厂实例PayServiceFactory factory getFactory(payType); // 这个 getFactory 怎么实现可以配合注册表、配置中心等 PayService payService factory.create();到这里你可能已经发现了工厂方法并没有消灭“选择工厂”的过程它只是把“根据条件创建产品”换成了“根据条件选择工厂”。那这样做有意义吗当然有。因为选择工厂的过程可以用注册表、配置中心、依赖注入容器来接管你完全可以把工厂注册到一个 Map 里这样新增支付方式时只需要新增一个产品类和一个工厂类再往 Map 里注册一条任何已有代码都不用改动。Component public class PayServiceFactoryRegistry { private final MapString, PayServiceFactory factoryMap new HashMap(); // 通过 Spring 构造器注入或者手动注册 public PayServiceFactoryRegistry(ListPayServiceFactory factories) { for (PayServiceFactory factory : factories) { factoryMap.put(factory.getType(), factory); } } public PayServiceFactory get(String payType) { return factoryMap.get(payType); } }工厂方法模式在实际工程里出现频率极高因为它天然契合依赖注入容器的玩法。你不需要写一长串 if-else只要让容器帮你把所有的工厂实现收集起来按名称注册用的时候直接查表。新增一个类型相当于往容器里加一个 Bean老代码一行不用动。这已经不仅是扩展性的问题了是工程上组织代码结构的最佳实践。不过也要说一句公道话如果项目里根本没有依赖注入容器也不打算引入工厂方法的价值密度就取决于你是否经常加类型。一年加不了两个类型为了“可能扩展”写一堆工厂子类其实是过度设计。先想清楚你的产品线到底会不会频繁变化再决定要不要上工厂方法。2.3 抽象工厂当产品之间存在“配套关系”时救你一命抽象工厂是三种里面最难理解、也最容易用错的一个。它解决的问题比工厂方法更进一步不光是创建单个产品而是创建“一组相互关联的产品”。比如同一套 UI 风格下的按钮、输入框、弹窗这三者在设计上是配套的你不能把 Windows 风格的按钮和 Mac 风格的输入框混在一起用否则界面会丑到你怀疑人生。抽象工厂的思路是定义一个大工厂接口这个接口里有多个创建方法每个方法对应一种产品具体子工厂负责生产“属于某个产品族”的一整套产品。public interface UIToolkitFactory { Button createButton(); TextInput createTextInput(); Dialog createDialog(); }public class WindowsUIToolkitFactory implements UIToolkitFactory { Override public Button createButton() { return new WindowsButton(); } Override public TextInput createTextInput() { return new WindowsTextInput(); } Override public Dialog createDialog() { return new WindowsDialog(); } }public class MacUIToolkitFactory implements UIToolkitFactory { Override public Button createButton() { return new MacButton(); } Override public TextInput createTextInput() { return new MacTextInput(); } Override public Dialog createDialog() { return new MacDialog(); } }这样有个很强的保障只要你在代码里始终依赖UIToolkitFactory接口你就永远不会把 Windows 的按钮和 Mac 的输入框混在一起用。因为整组产品是由同一个工厂生产出来的工厂内在逻辑保证了配套关系。抽象工厂的推广场景不需要很宏大。比如一个数据处理系统它要对接“关系型数据库”和“文件型数据源”这两大类存储。每一类存储都有完整的配套组件链接管理、查询执行、元数据读取。你不可能用 MySQL 的连接管理器去读 CSV 文件的元数据它们的实现体系是割裂的。抽象工厂就是把这些组件按“数据源类型”打包成两个族代码里要切换数据源时换一个工厂就整体切换。难点在哪在“加一个产品”的时候。如果是工厂方法加一种产品只需要加一个类但抽象工厂加一种产品比如上面UIToolkitFactory里要加一个新的Slider组件那么WindowsUIToolkitFactory 和 MacUIToolkitFactory 全部要跟着改。所以抽象工厂适合产品族稳定、族内产品稳定的情况——你要确保不会频繁往接口里加新方法否则所有子类都得动。3. 实操用一个完整的业务案例串起三种工厂光看代码片段很难建立直觉我用一个具体的实战案例把三种形态串起来。假设你在做一个“多平台内容发布系统”需要把同一篇内容同步发布到公众号、博客、知乎三个平台。每个平台都有不同的 API 封装类而且未来的确可能在增加平台比如小红书、即刻。这个业务非常适合用工厂模式来组织。3.1 第一版先定义产品接口别急着套模式不管最后选哪种工厂第一步永远是定义产品接口。这是重中之重很多人上来就写工厂产品抽象还没想明白结果建出来一堆孤立类。正确的姿势是先梳理需求所有平台发布器最终都要做什么答案是“提交内容并返回结果”。那接口就出来了public interface PlatformPublisher { PublishResult publish(Content content); }public class WechatPublisher implements PlatformPublisher { Override public PublishResult publish(Content content) { // 调用公众号接口处理素材上传、草稿箱等逻辑 return PublishResult.success(wechat); } }public class BlogPublisher implements PlatformPublisher { Override public PublishResult publish(Content content) { // 调用博客系统 API return PublishResult.success(blog); } }public class ZhihuPublisher implements PlatformPublisher { Override public PublishResult publish(Content content) { // 调用知乎创作中心接口 return PublishResult.success(zhihu); } }到这一步产品已经抽象得比较干净了。业务代码要发布内容时只需要持有PlatformPublisher引用调用publish(content)就能完成。发布器内部的实现更换、升级、重构调用方完全无感知。这是工厂模式能发挥作用的前提——如果产品连一个公共接口都抽象不出来工厂根本无从谈起。3.2 第二版用简单工厂快速支撑现有平台当前只有三个平台而且半年内大概率不会加新的。为了场景明晰、调用简单用一个简单工厂完全够用public class PublisherFactory { public static PlatformPublisher create(String platform) { switch (platform) { case wechat: return new WechatPublisher(); case blog: return new BlogPublisher(); case zhihu: return new ZhihuPublisher(); default: throw new IllegalArgumentException(unsupported platform: platform); } } }调用侧PlatformPublisher publisher PublisherFactory.create(platform); PublishResult result publisher.publish(content);这版本跑得非常顺滑。后续即使某个平台的上传逻辑调整了比如公众号的 API 增加了草稿箱参数你只需要在WechatPublisher内部改调用侧一行不动哪怕是WechatPublisher构造器加了参数也只需要在工厂内部适配老代码都稳如老狗。这层隔离带来的安全感只有被“改构造器影响全链路”坑过的人才懂。3.3 第三版平台多了升级成工厂方法半年后业务火起来了要接入小红书、快手后面还有一堆“正在洽谈”的平台。你会发现每次加平台都要去PublisherFactory里加一个 case简单工厂的类越来越多、越来越臃肿。更重要的是每次新增平台你都得修改这个工厂类——哪怕只是加一行也是“改动已有代码”。出 bug 的概率在增加代码审查的负担也在增加。这时就该升级成工厂方法了。为每个平台写一个独立工厂public interface PublisherFactory { PlatformPublisher create(); }public class WechatPublisherFactory implements PublisherFactory { Override public PlatformPublisher create() { return new WechatPublisher(); } }public class BlogPublisherFactory implements PublisherFactory { Override public PlatformPublisher create() { return new BlogPublisher(); } }然后建立一个注册表把平台名和工厂实例关联起来public class PublisherFactoryRegistry { private final MapString, PublisherFactory registry; public PublisherFactoryRegistry() { this.registry new HashMap(); registry.put(wechat, new WechatPublisherFactory()); registry.put(blog, new BlogPublisherFactory()); registry.put(zhihu, new ZhihuPublisherFactory()); // 新平台只需要在下面加一行 } public PlatformPublisher getPublisher(String platform) { PublisherFactory factory registry.get(platform); if (factory null) { throw new IllegalArgumentException(unsupported platform: platform); } return factory.create(); } }如果用 Spring 这类容器上面的注册表还能进一步简化——不需要手写 new 和 put直接用容器收集所有PublisherFactory类型的 Bean用每个工厂自报的平台名做 key。到了这一步新增平台的成本被压到最低新建一个产品类 一个工厂类然后向容器注册。旧代码一行不改注册表一行不加如果走自动收集这就是工厂方法相比简单工厂最实质的收益。注意从简单工厂升级到工厂方法不是在原类上小改而是结构性重构。如果项目业务正在迅速发展、平台名单肉眼可见地变长不如一开始就直接用工厂方法省得后面返工。这个判断很关键——你的时间不该花在“早该做了”的后悔上。3.4 终极形态抽象工厂在发布系统中的使用方式内容发布系统做到后面会碰一个新需求不仅要发布文章还要发布视频、图文、音频而且每个平台对每种内容类型都有不同的处理链路。公众号发图文、发视频、发音频都涉及不同API、不同的素材管理策略知乎、小红书也是如此。更麻烦的是平台之间还有联动——同一套内容在某个平台发图文成功以后可能才允许发视频。这时候你发现平台是一个“产品族”内容类型是“族内产品”。你要换平台就得同时换一整套针对该平台的文章发布器、视频发布器、音频发布器。这正是抽象工厂的典型场景。public interface PlatformFactory { ArticlePublisher createArticlePublisher(); VideoPublisher createVideoPublisher(); AudioPublisher createAudioPublisher(); }public class WechatPlatformFactory implements PlatformFactory { Override public ArticlePublisher createArticlePublisher() { return new WechatArticlePublisher(); } Override public VideoPublisher createVideoPublisher() { return new WechatVideoPublisher(); } Override public AudioPublisher createAudioPublisher() { return new WechatAudioPublisher(); } }public class ZhihuPlatformFactory implements PlatformFactory { Override public ArticlePublisher createArticlePublisher() { return new ZhihuArticlePublisher(); } Override public VideoPublisher createVideoPublisher() { return new ZhihuVideoPublisher(); } Override public AudioPublisher createAudioPublisher() { return new ZhihuAudioPublisher(); } }这样设计之后代码里发布整套内容的逻辑变成PlatformFactory factory getPlatformFactory(platform); ArticlePublisher articlePublisher factory.createArticlePublisher(); articlePublisher.publish(articleContent); VideoPublisher videoPublisher factory.createVideoPublisher(); videoPublisher.publish(videoContent);系统真正做到了“改一个 factory换一整套平台行为”而且永远不会出现“文章是公众号家的视频是知乎家的”这种混乱。整套发布链路天然成套。不过我得提醒一句抽象工厂虽然看起来“最强”实际工程里要慎重。你的代码里是否有真正的“产品族”概念决定了它是不是过度设计。如果不是为了维护配套关系只是单纯想“把创建逻辑抽出来”用工厂方法就够了。抽象工厂的引入条件应该是产品之间存在强约束性的搭配关系且这种关系必须被代码结构保证。没有这个前提抽象工厂只会给你增加一道类的数量负担。4. 避坑指南工厂模式最常见的五个坑与排查思路模式用对了是架构优化用错了就是代码灾难。下面这些坑我在实际项目里都见过也在代码评审中反复指出过。整理出来给你省点弯路。4.1 坑一工厂里面搞业务判断工厂不纯了这是最高频的问题。很多人写着写着就把业务逻辑写进工厂了。比如根据用户权限返回不同的支付方式根据订单金额判断用哪种支付渠道。这样做的结果是工厂里不光有“创建对象”的逻辑还有“选择规则”的逻辑。以后规则变化了要改工厂规则错了要查工厂工厂测试也得考虑业务分支复杂度直接翻倍。正确的做法是工厂只负责“给我一个类型我就返回对应的实现”至于这个类型是怎么来的是调用方的事情。选择逻辑可以放在调用方也可以提炼成独立的策略类但绝不能和创建逻辑混在一起。我自己的经验是每次想往工厂里加一个 if 时就问自己一句——“这个 if 是在决定创建谁还是在决定业务走向”是后者就拆出去。4.2 坑二无脑用抽象工厂类爆炸有些项目明明只是产品类型多一些根本没有“产品族”的概念硬是上了抽象工厂。结果一个发布系统里接口多了一倍实现类翻了两倍维护起来痛不欲生。特征很明显抽象工厂接口里的方法从来不会一起被使用调用侧每次都只调用其中的一两个。这个坑比第一个更难识别因为很多时候是“感觉以后可能用得上”才上的抽象工厂。我的建议很朴素没有两个以上的产品族别用抽象工厂族内产品没有配套关系更别用。“可能扩展”四个字不是设计金句它是过度设计最频繁的借口。4.3 坑三工厂返回具体类型把抽象接口晾在一边接手的项目里见过不少工厂方法签名直接写的是具体实现类public static WechatPayService create(String payType) { ... }这样写工厂创建出一个对象但调用方拿到的是具体的WechatPayService以后想替换实现、想加中间层全都无从谈起了。而且调用方一旦拿到了具体类就忍不住去调用一些只有它独有的方法慢慢产品接口的抽象就名存实亡了。正确签名永远是返回抽象类型PayService、PlatformPublisher、PlatformFactory。这个细节看起来人畜无害实际是工厂模式能否发挥价值的根本。你返回多抽象的层级决定了调用方被绑定得有多深。4.4 坑四把工厂当成万能解药忽略依赖注入在 Spring、Guice 这类依赖注入容器里工厂模式的使用姿势会有点微妙。很多时候你完全不需要自己写工厂类直接把产品实现注入到容器里用FactoryBean、Qualifier、Map 注入等方式就能实现“选择”的效果。我见过最夸张的写法项目里已经用了 Spring但开发依然手写了一个简单工厂类里面用 switch 创建对象。Spring 的 Bean 生命周期管理、AOP 增强、代理全部绕过了。这种代码不仅冗余还把容器的能力整废了。你的大原则应该是能用容器能力搞定的就别自己写工厂容器能力覆盖不到的再用工厂模式补位。两者不是互相替代而是在不同层面的协作。4.5 坑五测试的时候只测工厂成功路径工厂模式说白了是一个“根据输入返回对应对象”的映射逻辑这种逻辑非常适合做参数化测试。但很多人只测了“传入合法参数返回正确对象”的 happy path从来没测异常分支。结果一个非法字符串传入工厂本该抛出IllegalArgumentException偏偏因为漏写了 default 分支返回了 null调用方调publisher.publish(content)直接空指针而且报错信息根本看不出来是平台名写错了。给你的建议工厂类测试必须覆盖三类用例——合法参数返回正确对象、边界参数如空字符串、null行为明确、非法参数抛出明确异常。这三类跑完工厂这块逻辑才算真正锁死。特别是异常那一类宁可过度设计错误提示也不要吞掉异常返回空对象。空对象是万恶之源排查成本远高于一个明确报错。5. 一些关于工厂模式选择的个人心得三种工厂模式我实际用下来的体会简单归纳就三句话简单工厂负责“收敛”工厂方法负责“扩展”抽象工厂负责“成套”。你先判断自己的系统最缺哪个特性再决定上哪个模式。简单工厂适合那些“类型不多、变化不频繁、但创建逻辑确实需要统一管理”的小场景。它是三种模式里性价比最高的几乎没有什么学习成本又能立刻让调用方的代码干净一个量级。我自己的习惯是大部分中小型项目一个简单工厂就够用了。工厂方法是工程上最保险的选择。只要你的产品类型有一定的存在感并且未来有扩展的苗头直接上工厂方法不会后悔。结合依赖注入容器后工厂方法的扩展体验几乎是顺滑到无感的——加一类型加一个 Bean什么旧代码都不用碰。抽象工厂我建议谨慎使用。它解决的问题很具体但又很“重”——只有当产品之间存在强搭配约束时它才能真正发挥“防呆”的价值。现实中很多项目为了抽象而抽象最后换来的是类爆炸和维护地狱。真的有“产品族”需求时再上否则宁可先用工厂方法顶着等需求真实出现了再重构代价通常没那么大。以上是我对工厂模式的一次完整复盘。代码这东西模式是骨架业务才是血肉。无论工厂模式用得多么华丽最终要保住的都是业务侧的可读与可扩展而不是模式本身。希望这篇能帮你少走一段我走过的弯路在你项目的真实场景里找到适合自己那一种工厂。