☰
工厂模式实战解析:用解耦思维破解简单工厂、工厂方法与抽象工厂的应用难题
2026/10/14 3:23:08 网站建设 项目流程

我知道你迟早会碰见工厂模式这个名字。不管你是写 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 Map<String, PayServiceFactory> factoryMap = new HashMap<>(); // 通过 Spring 构造器注入或者手动注册 public PayServiceFactoryRegistry(List<PayServiceFactory> 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 Map<String, 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,什么旧代码都不用碰。

抽象工厂我建议谨慎使用。它解决的问题很具体但又很“重”——只有当产品之间存在强搭配约束时,它才能真正发挥“防呆”的价值。现实中很多项目为了抽象而抽象,最后换来的是类爆炸和维护地狱。真的有“产品族”需求时再上,否则宁可先用工厂方法顶着,等需求真实出现了再重构,代价通常没那么大。

以上是我对工厂模式的一次完整复盘。代码这东西,模式是骨架,业务才是血肉。无论工厂模式用得多么华丽,最终要保住的都是业务侧的可读与可扩展,而不是模式本身。希望这篇能帮你少走一段我走过的弯路,在你项目的真实场景里,找到适合自己那一种工厂。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询