工厂模式全解析:简单工厂、工厂方法与抽象工厂实战指南

发布时间:2026/10/7 11:44:39
工厂模式全解析:简单工厂、工厂方法与抽象工厂实战指南 设计模式里被问得最多、用得最勤、也被误解得最深的一个八成就是工厂模式。面试的时候刷题背八股总会背到“简单工厂、工厂方法、抽象工厂”这三个词但真拿到项目里去用很多人第一反应是这玩意不就是把new换了个地方吗甚至有人写完了还觉得不如直接new省事。我做了这么多年开发带过不少新人也帮人收拾过不少代码烂摊子可以拍着胸脯说工厂模式不是让你多写几行类而是帮你在“创建对象”这个最容易埋雷的地方把变化和耦合挡在门外。这篇文章就把工厂模式从头到尾拆开讲清楚包括三种工厂的差异、具体实现代码、什么时候该用什么时候别用以及在Unity游戏开发和Java后端里常见的落地姿势。不管你是准备期末大作业的学生、啃设计模式准备面试的求职者还是已经在项目里写业务代码的开发者这篇都值得你花十分钟认真过一遍。1. 工厂模式到底在解决什么问题1.1 从new说起对象创建为何容易失控几乎所有面向对象语言里创建对象的最直接方式就是new。但new本身有一个被很多人忽略的副作用它把“要创建什么”和“怎么创建”硬编码在了调用处。举个例子你做一个订单系统支持支付宝、微信、银行卡三种支付方式。最常见的写法是在业务代码里写switch判断if (payType.equals(alipay)) { payService new AlipayService(); } else if (payType.equals(wechat)) { payService new WechatPayService(); } else { payService new BankCardService(); }这段代码当时看着没毛病可一旦要新增一种支付方式比如云闪付你得跑到每一个下单入口去加一个else if。如果订单创建、退款、对账三个地方都写了这种判断那就是改三个地方漏一个线上就出bug。更麻烦的是有些对象的创建过程本身并不简单。比如要初始化一个数据库连接池得读配置文件、设置参数、校验合法性或者要创建一个游戏里的Boss怪物需要先加载模型资源、初始化血量和AI行为树。如果这些逻辑全部散落在各个调用方一旦初始化流程调整所有new过这个对象的地方都要跟着改。这就像你去餐厅吃饭每道菜都要自己跑后厨做一遍而不是把需求告诉服务员。工厂模式做的事情就是在你和具体菜品之间加一个“服务员”的角色。你只需要说“我要辣的”后厨工厂负责做出一道辣菜来你不关心这道菜用了什么锅、什么灶、什么流程。1.2 工厂的三个关键词封装、解耦、统一用术语来说工厂模式的核心价值可以压缩成三句话。第一封装创建逻辑。把对象创建的具体细节从调用方手里收走放到工厂里统一管理。调用方只需要告诉工厂它想要什么不需要关心对象是怎么new出来的。第二解耦使用与创建。业务代码不再依赖具体的产品类而是依赖产品接口或者抽象类。这样即使底层换了具体的实现类上层代码一行都不用动。第三统一控制入口。所有同类对象的创建都走同一个入口后续想加日志、加缓存、加参数校验、加对象池复用都只需要在工厂里改一处。有一个比喻我在给新人讲的时候屡试不爽工厂模式就是软件世界里的“前台接待”。你只需要说明需求接待员帮你安排对应的人来处理。你不需要知道那个人叫什么名字、工号多少、坐哪个工位。将来那个人离职了、换人了只要接待员还在你的业务照常运转。2. 三种工厂的定位与选型要点很多资料把工厂模式分成简单工厂、工厂方法和抽象工厂但有个细节很多人搞混——简单工厂其实并不是GoF四人组《设计模式》那本书里定义的正式模式它更像是一种编程习惯。但因为太好用了大家都默认把它收入工厂模式家族。2.1 简单工厂一个类搞定所有创建简单工厂的核心就是单独拿一个类负责所有产品的创建。刚才那三段if else收敛到一个类里业务代码就干净了public class PayServiceFactory { public static PayService create(String payType) { if (payType.equals(alipay)) { return new AlipayService(); } else if (payType.equals(wechat)) { return new WechatPayService(); } else { return new BankCardService(); } } }调用处变成一行PayService payService PayServiceFactory.create(payType);好处肉眼可见新增支付方式只改PayServiceFactory这一个类。坏处也在这里——每加一个新的支付渠道这个类的if else就膨胀一点。时间长了它会变成一个维护噩梦也就是所谓的“万能类”。我的经验是产品类型少于5个且短期内不太会剧烈扩张的场景用简单工厂性价比最高。写起来快调用方也直观。很多小项目直接这么干一点问题没有。2.2 工厂方法每个产品配一个工厂当产品种类多到简单工厂撑不住的时候就要上工厂方法了。它的思路很朴素既然工厂里堆太多判断不好维护那就每种产品建一个工厂让创建逻辑下放到子类。还是支付场景定义一个总工厂接口public interface PayServiceFactory { PayService create(); } public class AlipayServiceFactory implements PayServiceFactory { Override public PayService create() { // 支付宝服务的复杂初始化逻辑可以写在这里 return new AlipayService(); } } public class WechatPayServiceFactory implements PayServiceFactory { Override public PayService create() { return new WechatPayService(); } }调用时业务代码依赖的是PayServiceFactory接口具体用哪个工厂由上一层传入或者再套一个注册表来管理。这种做法的最大优点是符合开闭原则——新增一种支付方式不需要改已有工厂的代码直接新增一个工厂类就行。代价是类数量翻倍。原来5个产品1个工厂现在变成5个产品5个工厂1个接口。我个人的建议是当产品数量超过8个或者每个产品的创建逻辑差异很大比如有的要初始化一堆外部依赖有的只需要new一下用工厂方法能让每个工厂保持小而精。但如果产品创建逻辑都差不多只有类名不同那就没必要搞这个简单工厂加一个map照样顶得住。2.3 抽象工厂创建一组配套的产品抽象工厂是三种里最难理解、也最容易被人强行使用的一种。它的核心不是创建“一个产品”而是创建“一系列相关的产品”。比如跨平台UI组件。在Windows上你需要一套Windows风格的按钮、输入框、弹窗在macOS上你需要一套macOS风格的。这里的按钮、输入框、弹窗构成了一个“产品族”。如果用工厂方法你得分别管理buttonFactory、inputFactory、dialogFactory三个体系而且还得保证它们都属于同一个操作系统风格否则就会闹出Windows风格的按钮配上macOS风格的输入框这种乌龙。抽象工厂就是把这些家族绑在一起的工具public interface UIFactory { Button createButton(); Input createInput(); Dialog createDialog(); } public class WindowsUIFactory implements UIFactory { public Button createButton() { return new WindowsButton(); } public Input createInput() { return new WindowsInput(); } public Dialog createDialog() { return new WindowsDialog(); } } public class MacUIFactory implements UIFactory { public Button createButton() { return new MacButton(); } public Input createInput() { return new MacInput(); } public Dialog createDialog() { return new MacDialog(); } }调用方只需要选定一种UIFactory创建出来的所有UI组件保证风格统一。这里有个很重要的判断标准如果一堆对象之间天然就存在“配套关系”那才适合用抽象工厂。如果它们之间没有任何关联只是恰好都属于同一类型那抽象工厂就是画蛇添足。我见过不少人把抽象工厂用成了工厂方法的加强版反而把接口搞得臃肿无比。三种工厂的差异一句话总结就是简单工厂管一个产品的多种实现选择工厂方法管每个产品自己的创建细节抽象工厂管一组产品的配套创建。3. 实操过程与核心环节实现理论说再多不如把代码跑起来。这一节我带你把三种工厂模式完整实现一遍分别用Java和C#Unity环境各写一个贴近实战的例子。你不仅能看到代码长什么样还能看到设计思路是怎么一步步演进的。3.1 Java实现一个业务系统的工厂演进假设我们在做一个游戏角色创建系统角色有三个职业战士Warrior、法师Mage、射手Archer。每个角色都需要设置初始属性、技能列表和专属装备。先定义一个角色接口public interface GameCharacter { void initAttributes(); void initSkills(); void initEquipment(); }然后是三个具体角色类。为了避免代码冗长这里只展示战士的实现其他两个结构相同public class Warrior implements GameCharacter { Override public void initAttributes() { System.out.println(战士高血量高物理攻击); } Override public void initSkills() { System.out.println(战士狂暴斩、盾墙); } Override public void initEquipment() { System.out.println(战士双手剑、重甲); } }第一步用简单工厂包一层public class CharacterFactory { public static GameCharacter create(String type) { switch (type) { case warrior: return new Warrior(); case mage: return new Mage(); case archer: return new Archer(); default: throw new IllegalArgumentException(未知角色类型: type); } } }调用端非常简单GameCharacter player CharacterFactory.create(mage); player.initAttributes(); player.initSkills(); player.initEquipment();这个版本已经完全能用了。但如果后续要加新职业比如刺客你就得修改CharacterFactory的switch。对于一个角色类型相对固定的游戏来说这个改动的频率不高可以接受。第二步我们假设游戏持续迭代每两周一更新两个多月后出现了二十几种职业而且角色构造逻辑变得很复杂战士需要额外加载一个怒气系统法师需要绑定元素亲和度射手需要生成宠物。这时候简单工厂的switch就膨胀得没法看了。于是重构为工厂方法public interface CharacterFactory { GameCharacter create(); } public class WarriorFactory implements CharacterFactory { Override public GameCharacter create() { Warrior w new Warrior(); w.setRageSystem(new RageSystem()); return w; } } public class MageFactory implements CharacterFactory { Override public GameCharacter create() { Mage m new Mage(); m.setElementAffinity(randomElement()); return m; } }之前写在简单工厂里的那一坨创建序列现在被均匀地分配到各个专业工厂里。新增一个职业只需要新增一个Character类加一个对应的Factory类不再触碰任何已有代码。3.2 Unity中的工厂模式怪物生成与对象池Unity开发里工厂模式最典型的应用就是怪物生成系统。尤其在做RPG或者动作游戏时逻辑上要把“怪物在哪里生成的关卡设计”和“怪物怎么初始化的数据逻辑”分开。比如我们有三种怪物普通僵尸、精英蝎子、Boss巨龙。用C#写一个简单工厂public enum MonsterType { Zombie, Scorpion, Dragon } public class MonsterFactory : MonoBehaviour { public GameObject zombiePrefab; public GameObject scorpionPrefab; public GameObject dragonPrefab; public GameObject CreateMonster(MonsterType type, Vector3 spawnPosition) { GameObject prefab type switch { MonsterType.Zombie zombiePrefab, MonsterType.Scorpion scorpionPrefab, MonsterType.Dragon dragonPrefab, _ null }; if (prefab null) { Debug.LogError(未配置的怪物类型: type); return null; } GameObject monster Instantiate(prefab, spawnPosition, Quaternion.identity); // 初始化怪物的血量、AI、掉落物等 var config GetMonsterConfig(type); monster.GetComponentMonsterBase().Init(config); return monster; } }Unity里有个细节值得展开Instantiate是Unity创建GameObject的核心API但它并不是没有代价的。大量动态创建和销毁会产生内存碎片而且GC压力增大。这时候工厂模式就更显价值了因为你可以把对象池逻辑优雅地嵌入工厂内部。对象池复用的工厂大致长这样public class MonsterFactoryWithPool : MonoBehaviour { private DictionaryMonsterType, QueueGameObject pools new(); public GameObject GetOrCreateMonster(MonsterType type, Vector3 pos) { if (pools[type].Count 0) { GameObject go pools[type].Dequeue(); go.SetActive(true); go.transform.position pos; return go; } return CreateMonster(type, pos); } public void RecycleMonster(MonsterType type, GameObject go) { go.SetActive(false); pools[type].Enqueue(go); } }这个设计在游戏开发里非常实用。调用方根本不知道拿到的怪物是刚创建的还是从对象池复用的工厂完全屏蔽了这层差异。3.3 从简单工厂到抽象工厂一套处理多套产品族如果从后端视角看抽象工厂最经典的案例是数据库访问。系统要支持MySQL和PostgreSQL两种数据库而且每一种数据库都会配套自己的连接、命令、事务处理器。这些组件是一套的混搭不现实。按照抽象工厂的思路public interface DatabaseFactory { Connection createConnection(); Command createCommand(); Transaction createTransaction(); } public class MySqlFactory implements DatabaseFactory { public Connection createConnection() { return new MySqlConnection(); } public Command createCommand() { return new MySqlCommand(); } public Transaction createTransaction() { return new MySqlTransaction(); } } public class PostgreSqlFactory implements DatabaseFactory { public Connection createConnection() { return new PostgreSqlConnection(); } public Command createCommand() { return new PostgreSqlCommand(); } public Transaction createTransaction() { return new PostgreSqlTransaction(); } }看到这里你应该感受到抽象工厂其实就是工厂方法和“产品族约束”结合的产物。它不关心单个产品的怎么创建而是保证一族产品的一致性。实际项目里如果系统只需要一种数据库千万别为了“以后能扩展”硬写这套抽象。YAGNI原则You Arent Gonna Need It你不会需要它在这里同样有效。抽象工厂真正的使用时机是你在同一套代码里确实要处理多套互相配套的方案而且切换频率不低时才值得投入。3.4 实操细节工厂实现的几个关键决策工厂模式写起来本身不难但有几个决策点决定了它好不好用。第一个决策工厂要不要做成单例如果工厂是无状态的所谓无状态就是它内部没有需要保存的变量每次create的输入输出完全确定那完全可以用静态方法像前面Java例子那样。但如果你在Unity里工厂需要持有prefab引用、对象池字典这些状态那就必须是实例而且往往做成MonoBehaviour单例挂在场景里。第二个决策create方法的参数怎么设计强力建议用字符串或者枚举传入而不要用Class类型直接传入。例如create(warrior)比create(Warrior.class)更容易做参数校验也更便于后续接入配置表。有经验的开发者还会把参数做成一个config对象携带更多创建信息。第三个决策工厂内部要做什么校验这一点被很多人忽略。工厂是创建对象的守门员必须承担参数校验的职责。传入非法类型应该抛出有明确提示的异常而不是返回null。前面Java代码里我就写了throw new IllegalArgumentException(未知角色类型: type)这不是摆设在实际运行中能帮你省掉无数定位问题的时间。第四个决策产品类型全在工厂里写if代码很丑怎么办如果你觉得switch太丑可以用注册表模式优化——用一个Map把类型关键词映射到创建逻辑public class CharacterFactory { private static final MapString, SupplierGameCharacter CREATORS new HashMap(); static { CREATORS.put(warrior, Warrior::new); CREATORS.put(mage, Mage::new); CREATORS.put(archer, Archer::new); } public static GameCharacter create(String type) { SupplierGameCharacter creator CREATORS.get(type); if (creator null) { throw new IllegalArgumentException(未知角色类型: type); } return creator.get(); } }这个写法在Java 8之后很常见可读性和扩展性都很好。新增职业时往Map里注册一条即可符合开闭原则又不像工厂方法那样每个产品都要写一个类。4. 实战中的常见问题与排查技巧实录4.1 什么时候不要用工厂模式这个部分我觉得比怎么用还重要。项目里见过太多人为模式而模式的情况了。如果产品类本身非常简单比如就是个纯粹的数据载体字段一堆、逻辑没有那直接new就好。工厂模式在这种场景下创造的收益接近零还多了一个间接层阅读代码时反而要多跳一次调用。另外有一个场景我也主张别用产品类型短时间不打算变整个项目生命周期内可能就两三个类型。如果你判断产品数量不会增长老老实实new是最直接的做法不必为了面试里答得好而在生产代码里过度设计。判断用不用的一个通用尺度去看调用方是不是真的不关心具体类型。如果调用方拿到对象之后立马if (obj instanceof A)做类型判断然后走完全不同的分支那说明产品抽象层设计得有问题工厂模式救不了你你得先理清产品接口该暴露什么方法。4.2 工厂里的初始化逻辑不要塞太多工厂背后往往藏着复杂的初始化流程读配置、连外部服务、加载资源。这些逻辑放在工厂里没问题但要注意一点——工厂最好保持职责简单一个create就是一个产品的完整装配。我踩过一个坑某次在工厂里顺手加了缓存逻辑create出的对象被缓存起来第二次调用直接返回同一个实例。当时没觉得有什么问题直到排查一个状态污染的bug发现两个业务模块共享了同一个对象改一个全变了。从那以后我给自己立了个规矩工厂只负责创建和装配缓存、代理、装饰逻辑必须放在工厂外层或者用装饰器包一层。这样出了问题排查范围能缩小一大半。4.3 快速定位工厂相关的疑难问题工厂模式带来的一个常见困惑是报错信息里看不到“是谁创建了这个对象”。如果一个对象在运行时行为异常你不知道它从哪里来。这里分享一个实用排查技巧在工厂的create方法里为产品的关键字段打印一行创建日志。日志格式包含产品类型和调用来源标记public static GameCharacter create(String type) { LOGGER.info(Create character, type{}, type); ... }别小看这行日志。生产环境里对象创建是高频操作排查问题时只要从日志里找到最后一次create调用就能锁定调用链。大量新人调试半天找不到对象来源就是缺了这一手。如果产品对象有状态异常也可以考虑给产品类加一个sourceFactory字段创建时打上工厂标记。虽然这严格来说不属于工厂模式的范畴但确实能省很多排查时间算是一个野路子技巧。4.4 面试和期末答辩时如何把工厂模式讲清楚热搜词里出现“设计模式期末”“设计模式大作业”这些词说明很多读者是在为考试或者答辩做准备。如果要在面试或答辩中把工厂模式讲出让面试官眼前一亮的水平光背定义是不够的得有一个清晰的递进。先回答“工厂模式解决什么问题”创建延迟到子类、解除调用方与具体类的耦合然后用一个具体的演变过程说话——比如从简单工厂到工厂方法因为场景变化产品剧增、创建逻辑趋向复杂来引出重构动机最后再提抽象工厂用它解决产品族的问题。面试官最爱问的几个变体问题我在结尾统一整理一下简单工厂和工厂方法的本质区别是什么答简单工厂通过参数判断创建哪种产品用if/switch解决选择问题扩展靠修改工厂类工厂方法通过继承把创建逻辑推给子类扩展靠新增子类。工厂模式跟建造者模式有什么区别答工厂模式侧重选择实现类通常一步返回一个产品建造者模式侧重分步骤构建复杂对象包含大量参数校验和装配过程。Spring的BeanFactory是不是工厂模式答是而且是个综合的工厂模式应用——按名称和类型获取对象具体创建逻辑由容器管理还叠加了单例、代理等很多特性。答题时如果能配一套演进代码展示给面试官看说服力比干讲强得多。5. 我对工厂模式的一些学习体会前面几节把工厂模式的知识点基本覆盖完了最后聊几句我在实际开发中形成的一些认识。第一设计模式是用来解决问题的不是用来数数目的。23种设计模式我真正在业务里高频用到的一只手数得过来。工厂模式是我最常推荐的入门模式因为它最简单直白地体现了“把变化封装起来”的思想。只要你项目里出现了“根据类型创建对象”和“创建流程复杂”这两个信号第一时间就该想到它。第二模式之间是配合使用的不是互斥的。工厂模式经常跟单例、策略、模板方法等一起出现。比如策略模式用在产品内部算法切换工厂模式负责把正确策略的实现类组装出来。Unity项目里工厂和对象池的搭配也能看出这一点各管一段互不干扰。第三代码是给人读的。后来我review别人的代码时判断一个工厂用得好不好不是看类图多标准而是看新人接手时能不能在三分钟内看懂创建链路。如果为了贴一个模式标签引入大量抽象反而让阅读成本暴增那就是本末倒置。如果你正在啃设计模式我的建议是不要为了用而用找一个你自己项目里重复出现“根据条件new对象”的代码段试着用简单工厂先收拢它你会发现代码清爽很多。积累了真实的体感之后工厂方法、抽象工厂的适用场景自然就清晰了。