工厂模式详解:从简单工厂到抽象工厂的设计思路与实战
工试云启 考证服务中心整理

写代码写到一定阶段你会遇到一个尴尬的场面业务代码里到处都是new关键字产品经理提了个新需求说以后用户的欢迎消息不只有短信还要加邮件和站内信。你顺着new关键字一个个找下去翻遍几十个类逐个去改。如果哪天漏掉一个地方线上就会出现“该发站内信的发了短信”的怪事。工厂模式Factory Pattern就是用来收拾这种局面的它是 23 种设计模式里创建型模式的代表核心思想只有一句话把对象的创建和使用拆开。这篇文章我会从最常用的简单工厂讲起逐步讲到工厂方法模式、抽象工厂模式结合 Java 和 Python 的写法、软考设计模式速记、面试常见问题把工厂模式讲透。适合两类人一类是刚学设计模式的初学者想搞明白“工厂到底是干嘛的”另一类是准备软考或技术面试、想快速查漏补缺的同学。看完这篇文章你至少能分清三种工厂模式的区别并知道在真实项目里该选哪种。1. 工厂模式到底在解决什么问题——设计思路拆解1.1 工厂三兄弟简单工厂、工厂方法、抽象工厂工厂模式不是单一的一种而是一个家族。市面上聊工厂模式通常说的是三个东西简单工厂Simple Factory严格说它不是 GoF 23 种设计模式之一但它是学习工厂模式的必经入口。核心是一个工厂类根据参数返回不同的产品对象。工厂方法Factory Method这是正经的 GoF 模式。定义创建对象的接口让子类决定实例化哪个具体类。抽象工厂Abstract Factory也是正经的 GoF 模式。提供一个接口用于创建“一组相关或相互依赖的对象”而不需要指定它们的具体类。用一个“吃饭”的类比帮你先建立直觉简单工厂 一个食堂后厨你说要什么菜后厨按单出餐工厂方法 连锁餐饮公司每个片区有自己的厨房但都遵循同一个“出餐标准”抽象工厂 一套连锁体系每家门店必须提供统一的一整套菜品组合你点套餐配菜不会串味。这个直觉很重要。后面所有代码和场景都是围绕这层关系展开的。1.2 “创建”和“使用”拆开的价值直接new对象有什么问题很多人刚学的时候没感觉觉得“反正都要用何必绕一圈”。我举三个实际痛点第一调用方和具体类耦合。你的代码写着new EmailService()那你的代码就永远离不开EmailService这个类。哪天产品说“我们不用邮件了改成钉钉推送”你就得把代码里所有new EmailService()的地方全改一遍。全局替换看着简单但真实项目里这些new可能分散在十几个模块里有的还有条件判断改起来极易出错。第二构造逻辑泄漏给了调用方。创建一个对象往往不是简单new一下就完的。可能要读配置、填参数、校验权限、甚至从连接池里借资源。这些“准备工作”如果散落在调用方调用方的代码会越来越重而且不同调用方很容易写出不一致的创建逻辑。第三违背开闭原则。新增一种产品就要打开已有代码去改。改动越频繁回归风险越大。工厂模式的解决思路非常直接所有创建动作收敛到一个“中间人”那里。调用方只说“我要一个日志记录器”从不问“这个日志记录器是怎么来的、用什么类实现的”。创建逻辑变复杂了改工厂内部新增产品类型了扩展工厂。调用方始终稳如泰山。1.3 工厂模式在 23 种设计模式中的位置23 种设计模式分成创建型、结构型、行为型三大类。工厂模式属于创建型这类模式研究的核心问题就是“对象怎么被生出来”。创建型模式一共五种单例、工厂方法、抽象工厂、建造者、原型。软考设计模式速记里有个口诀叫“单工抽建原”——单例、工厂、抽象工厂、建造者、原型顺序可以按自己习惯记但五个名字必须滚瓜烂熟。面试被问到“创建型模式有哪些”这五种一个都不能漏。简单工厂因为不算 GoF 模式所以在软考和面试里很容易被拿来挖坑后面第 6 章会专门聊这个坑。2. 简单工厂模式入门第一步2.1 核心结构与适用场景简单工厂的核心结构是三个角色产品接口、多个产品实现类、一个工厂类。产品接口定义产品的行为比如Logger接口的log()方法产品实现类比如ConsoleLogger、FileLogger工厂类提供一个create(type)方法根据传入参数来决定返回哪一个实现类。它的适用场景有两个硬性前提产品种类不多且创建逻辑不复杂。比如一个工具库内部封装给上层提供统一的日志入口这种场景简单工厂非常合适。但如果你负责的系统里有几十种产品简单工厂的if-else会膨胀得厉害那时候就该往工厂方法或者注册表方向改造。2.2 Java 代码实现日志记录器以一个日志记录器为例。需求是支持控制台日志和文件日志两种方式调用方只需要传入类型就能拿到对应的 Logger。// 产品接口 public interface Logger { void log(String message); } // 具体产品 public class ConsoleLogger implements Logger { Override public void log(String message) { System.out.println([Console] message); } } public class FileLogger implements Logger { Override public void log(String message) { System.out.println([File] message); } } // 工厂类 public class LoggerFactory { public static Logger createLogger(String type) { if (console.equalsIgnoreCase(type)) { return new ConsoleLogger(); } else if (file.equalsIgnoreCase(type)) { return new FileLogger(); } throw new IllegalArgumentException(未知的日志类型: type); } }客户端这样用Logger logger LoggerFactory.createLogger(file); logger.log(写入一条日志);注意两个细节。第一createLogger方法我用了static这也是简单工厂常被叫做“静态工厂”的原因。第二客户端只依赖Logger接口从来不依赖ConsoleLogger、FileLogger这两个具体类。以后把FileLogger的实现从写本地文件改成写远程日志服务调用方完全无感。但简单工厂的问题也写在代码里了你新增一种日志类型必须打开LoggerFactory修改if-else。这就是明显的“对修改开放对扩展不友好”也是它进不了 GoF 模式名单的核心原因。2.3 Python 等语言的手写对比与注意事项Python 写简单工厂可以更清爽推荐用字典映射替代if-else链条class ConsoleLogger: def log(self, message): print(f[Console] {message}) class FileLogger: def log(self, message): print(f[File] {message}) class LoggerFactory: _loggers { console: ConsoleLogger, file: FileLogger, } classmethod def create(cls, logger_type): logger_cls cls._loggers.get(logger_type) if not logger_cls: raise ValueError(f不支持的日志类型: {logger_type}) return logger_cls()用法是一样的LoggerFactory.create(console)。Python 没有强制类型系统工厂写法更自由但也更容易被玩坏。比如有人会把字典直接暴露给调用方让调用方自己取类来实例化那就失去封装的意义了。C 里有个额外话题简单工厂返回裸指针new出来的对象谁来释放我建议返回std::unique_ptr或std::shared_ptr避免内存泄漏。谁创建、谁负责生命周期这是 C 工厂绕不开的设计问题。Java 因为有垃圾回收不用操这个心但也正因如此很多 Java 初学者会忽略对象创建背后的资源管理成本等到写数据库连接池的时候才被打回原形。3. 工厂方法模式把决定权交给子类3.1 工厂方法与简单工厂的本质区别从这句话开始理解工厂方法“定义一个用于创建对象的接口让子类决定实例化哪一个类”。简单工厂是“一个工厂类管所有产品”工厂方法是“每种产品对应一个工厂子类”。前者靠参数判断后者靠多态。我用一张表给你比清楚对比维度简单工厂工厂方法工厂角色一个工厂类统一创建抽象工厂类 多个工厂子类创建方式传入参数工厂内部分支判断调用不同工厂子类的方法新增产品修改已有工厂类新增一个工厂子类不改旧代码是否符合开闭原则不符合符合类数量少多适合场景产品类型少、变动少产品类型多、变动频繁工厂方法模式的“精髓”在方法层面的多态客户端手里只有抽象工厂引用运行时拿到的却是具体子类工厂创建出来的也是对应产品。整个过程客户端无感。3.2 Java 代码实现消息推送渠道假设要做一个消息推送系统目前支持邮件、短信、站内信三种渠道。我先定义产品接口public interface MessageSender { void send(String to, String content); } public class EmailSender implements MessageSender { Override public void send(String to, String content) { System.out.println(发送邮件给 to : content); } } public class SmsSender implements MessageSender { Override public void send(String to, String content) { System.out.println(发送短信给 to : content); } } public class InAppSender implements MessageSender { Override public void send(String to, String content) { System.out.println(发送站内信给 to : content); } }再定义抽象工厂和三个子工厂public abstract class MessageSenderFactory { public abstract MessageSender createSender(); } public class EmailSenderFactory extends MessageSenderFactory { Override public MessageSender createSender() { return new EmailSender(); } } public class SmsSenderFactory extends MessageSenderFactory { Override public MessageSender createSender() { return new SmsSender(); } } public class InAppSenderFactory extends MessageSenderFactory { Override public MessageSender createSender() { return new InAppSender(); } }客户端这样用MessageSenderFactory factory new EmailSenderFactory(); MessageSender sender factory.createSender(); sender.send(zhangexample.com, 你好);现在产品经理说下个版本要加钉钉推送渠道。你只需要做两件事public class DingTalkSender implements MessageSender { Override public void send(String to, String content) { System.out.println(发送钉钉消息给 to : content); } } public class DingTalkSenderFactory extends MessageSenderFactory { Override public MessageSender createSender() { return new DingTalkSender(); } }已有的EmailSenderFactory、SmsSenderFactory一行都不动。新增代码而不是修改旧代码这就是开闭原则最直接的味道。3.3 工厂方法模式的扩展技巧实际项目里工厂方法常和单例结合。有些对象创建成本高没必要每次createSender()都new一遍可以在工厂子类里缓存实例public class EmailSenderFactory extends MessageSenderFactory { private static final EmailSender INSTANCE new EmailSender(); Override public MessageSender createSender() { return INSTANCE; } }另外如果不想每种渠道都单建一个工厂类可以用“注册表 工厂方法”减少类数量。用一个Map做登记注册表持有的是各个工厂子类实例MapString, MessageSenderFactory registry new HashMap(); registry.put(email, new EmailSenderFactory()); registry.put(sms, new SmsSenderFactory()); MessageSenderFactory factory registry.get(sms);这样客户端从“写死一个工厂子类”升级为“按配置获取工厂”扩展性又上了一个台阶。但要提醒一句反射 字符串配置虽然灵活代价是可读性和运行期错误排查难度变差项目里要用的话必须在团队内约定好配置文件规范。4. 抽象工厂模式产品家族的组合拳4.1 产品族的含义与适用场景工厂方法解决的是“一条产品线”的问题抽象工厂解决的是“多条产品线 产品族”的问题。什么是产品族就是多个产品之间有配套关系必须成套使用。比如开发一个跨平台的 UI 组件库按钮要和文本框配一套风格不可能一个主题里按钮是拟物风、输入框是扁平风。再比如数据库访问层不同数据库有各自的连接对象、语句对象、结果集对象这些对象来自同一个数据库驱动不能乱搭。这种场景就是抽象工厂的用武之地。抽象工厂的定义是提供一个接口用于创建相关或依赖对象的家族而不需要指定具体类。关键就在“家族”两个字——它创建的是一组配套产品而不是单一个体。继续用餐饮类比你把工厂方法想成“做一碗面的厨房”抽象工厂就是“整套连锁餐饮体系”每个门店不仅提供面还要配套供应小菜、饮品、甜品而且形式上必须统一。4.2 Java 代码实现跨平台 UI 控件写一个简单的主题化表单控件。现有两种主题亮色Light和暗色Dark。一种主题必须产出配套的按钮和输入框。第一步定义产品接口public interface Button { void render(); } public interface Input { void render(); }第二步实现两套具体产品public class LightButton implements Button { Override public void render() { System.out.println(渲染亮色主题按钮); } } public class DarkButton implements Button { Override public void render() { System.out.println(渲染暗色主题按钮); } } public class LightInput implements Input { Override public void render() { System.out.println(渲染亮色主题输入框); } } public class DarkInput implements Input { Override public void render() { System.out.println(渲染暗色主题输入框); } }第三步定义抽象工厂和两个具体工厂public interface UIFactory { Button createButton(); Input createInput(); } public class LightUIFactory implements UIFactory { Override public Button createButton() { return new LightButton(); } Override public Input createInput() { return new LightInput(); } } public class DarkUIFactory implements UIFactory { Override public Button createButton() { return new DarkButton(); } Override public Input createInput() { return new DarkInput(); } }客户端拿到的永远是同一套主题的产品UIFactory factory new DarkUIFactory(); Button button factory.createButton(); Input input factory.createInput(); // button 和 input 一定都是暗色系抽象工厂有个在面试里必被问的经典问题“为什么新增一个产品类型很痛苦”假设 UI 体系要新增一个下拉框组件UIFactory接口要加createSelect()方法那LightUIFactory和DarkUIFactory都得改所有已有工厂子类全部波及。抽象工厂适合产品族内部保持不变、整体不频繁变动的场景如果产品体系天天加新类型抽象工厂会让你痛不欲生。4.3 三个工厂模式的关系怎么记忆这三个模式经常把人绕晕我送你一个口诀“简·方·抽”三个字对应三种模式的核心职责简单工厂管“一个产品的多种实现”靠参数分流工厂方法管“一个产品线”把工厂拆成多个子类靠继承和多态分流抽象工厂管“多个产品线”保证整套产品配套一致靠接口组合分流。软考设计模式速记常考的就是这几个模式的辨识。看到描述里出现“根据类型创建对象”优先考虑简单工厂出现“由子类决定实例化哪个类”优先考虑工厂方法出现“产品族”“相互关联的对象”“成套”优先考虑抽象工厂。这三个关键词直接对应三兄弟。5. 真实项目里的工厂模式实战经验5.1 值得使用工厂模式的高频场景工厂模式不是银弹但它有几个非常成熟的应用领域遇到这些场景基本可以放心上一是框架和中间件对外提供创建入口。最典型的例子是 JDBC 的DriverManager.getConnection()还有 ORM 框架的SessionFactory、线程池的Executors工具类。这些场景的共同点是创建过程复杂、涉及资源管理框架作者必须把创建细节收进内部。二是同一接口多实现的场景。消息推送渠道、日志记录器、数据转换器、文件解析器——只要一个接口下面挂了好几个实现类且业务上需要按配置选择实现工厂就是最自然的方案。三是创建过程需要统一控制的场景。比如全局只允许存在一个连接池对象创建之前必须读取配置、校验参数、检查环境。把这一整套“初始化仪式”放进工厂调用方永远只调一个入口不会有人绕过校验。四是我最近踩的一个真实案例一个数据同步模块早期代码在流程里到处直接new解析器后来新增了一个 Excel 解析器结果发现有三处调用点都要跟着改其中有处还因为漏改导致线上解析逻辑新旧混用。重构为工厂方法之后新增解析器只需要新增一个工厂子类测试也只用对着新类写就行。这个体验让我对“创建逻辑收敛”有了很深的体感。5.2 常见误区和踩坑实录误区一只有一个实现也套工厂。我见过有人给一个“只有一个实现类的服务”加了工厂类理由是“以后肯定要扩展”。这纯属为了用而用。工厂模式的价值在于解决重复创建逻辑或多实现问题只有一个实现时工厂只是平白增加阅读成本。等第二个实现真的出现时再重构成本并不高。误区二简单工厂的if-else堆到几百行。产品种类一多简单工厂内部会变成一座屎山。解决办法是升级为工厂方法或者用注册表模式替代分支判断。判断标准是每新增一个产品你改的是“旧代码”还是“新代码”改旧代码就说明工厂的形状出了问题。误区三把抽象工厂用在产品体系频繁扩张的地方。前面说了抽象工厂新增产品类型要改所有工厂子类如果业务处于快速演进期抽象工厂会让你每走一步都在补洞。坑反射 字符串创建对象配置写错只有运行期才暴露。用配置驱动工厂没问题但配置千万别直接用字符串映射到类名否则一个拼写错误在编译期根本发现不了只能等运行时报ClassNotFoundException。我的建议是配置值用简单的枚举或常量约束工厂内部再做一层校验。还有个经常被忽视的提醒工厂模式会让类数量变多这是正常代价但不要让工厂类本身成为新的“上帝类”。如果某个工厂里同时管着几十类产品的创建逻辑就该考虑拆分成多个按职责划分的工厂。6. 软考、面试与大作业中工厂模式怎么准备6.1 软考设计模式速记与判断题技巧软考对工厂模式的考法很套路无非三种给 UML 类图让你识别模式给需求描述让你选模式给类图缺空让你补类名或方法名。做题时有三个速记锚点类图里只有一个工厂类并且工厂类里有个带参数的方法指向多个产品类这是简单工厂类图里工厂角色是个抽象类或接口下面挂着多个工厂子类每个工厂子类连向一个具体产品这是工厂方法类图里一个抽象工厂接口有多个创建方法每个方法连向不同系列的产品接口这是抽象工厂。还有一个高频陷阱题目把“简单工厂”算作 23 种设计模式之一让你判断对错。答案是错。简单工厂不是 GoF 模式这点在软考里反复拿来挖坑。另外要注意和“建造者模式”区分。工厂模式强调用工厂对象直接产出成品建造者模式强调分步骤构建一个有内部依赖关系的复杂对象客户端可以在构建过程中加步骤。考场上一句话说“分步骤创建对象”立刻往建造者那边想别被“创建”两个字带偏。6.2 面试高频问题与避坑指南面试里工厂模式基本是必考题我把常见问题梳理成一份自检清单你面试前对着看一遍问为什么有了工厂方法还需要抽象工厂答工厂方法解决“一个产品线”的对象创建问题抽象工厂解决“多个产品线组成的产品族”的配套创建问题。当业务里要求一批产品必须成套出现时工厂方法管不了这种一致性约束抽象工厂才能从入口上保证。问Spring 里的BeanFactory和FactoryBean有什么区别这是个经典延伸题。BeanFactory是 IoC 容器的核心接口管理一堆 Bean 的生命周期和依赖关系FactoryBean本身是个特殊 Bean它是个用来生成其他 Bean 的工厂属于工厂模式的框架级应用。两者名字像但职责完全不同。问你项目中哪里用了工厂模式怎么选型不要背定义建议直接讲一个自己重构过的真实案例。注意说清楚三个点重构前哪里痛工厂把创建逻辑收敛到了哪里新增需求时改了什么。面试官要的是“有判断力”不是“背答案”。问工厂模式和策略模式怎么区分最简洁的回答工厂模式管“对象怎么创建”策略模式管“算法怎么选择执行”。两者经常组合出现但角色不同。建造者模式则别和工厂混它的关注点是“复杂对象的逐步组装”。能在一分钟内把这三个模式的边界说清楚这题就稳了。6.3 设计模式大作业怎么选型与写代码很多同学做设计模式大作业最后做成“模式展示橱窗”一个类图里塞七八个模式结果每个都用得很浅。我的建议是主线场景选一个有明显“多实现”需求的项目比如图形编辑器支持多种图形绘制、考试系统支持多种题型、购物车优惠计算支持多种优惠策略。然后围绕主线用工厂模式解决核心对象创建的问题再用一个其他模式辅助就够了。落地步骤建议三条第一步画 UML 类图。把你的产品接口、具体产品、工厂类都画清楚注意箭头方向产品实现类指向接口画空心三角虚线工厂类指向产品接口画虚线。这一步在课设答辩里占分很重。第二步写产品接口和具体实现类。一定要保证“每个具体产品都实现了同一个行为接口”这是工厂能统一返回的前提。第三步写工厂类并改造客户端。改完之后你必须能在答辩时说清楚一句话“在调用方我只依赖了产品和工厂两个抽象角色新增产品时不需要修改已有代码。”这句话说完评委基本就知道你是真的理解工厂模式了。常见扣分点我也提一句有同学把工厂类的方法写得特别大里面放了大量if-else这不算错但答辩时评委一定会问“如果要新增产品你改哪里”答“改工厂方法”就只能拿基础分答“新增一个子类不改旧代码”才是工厂方法的进阶分。我写过很多设计模式相关的文章最后聊点个人的真实感受。工厂模式是所有设计模式里最容易被记住、也最容易被用错的。它的本意是“把创建复杂度收敛起来让调用方专注于使用”但它解决不了“你根本不知道该不该用”的问题。我的经验是当你开始为创建对象写第三份相同的初始化代码时再上工厂也不迟。在此之前老老实实写new反而更健康。学这个模式最快的方式不是背定义而是找一段没有工厂的代码亲手把它重构成工厂模式然后把新增一个产品类的全过程走一遍。那种“旧代码一行没动新功能就加上了”的体验比任何定义都记得牢。