简单工厂模式入门:用披萨店案例讲透对象创建与解耦

发布时间:2026/10/27 6:18:27

简单工厂模式入门:用披萨店案例讲透对象创建与解耦 很多刚接触设计模式的朋友第一次看到简单工厂这四个字脑子里冒出来的往往是教科书上那张干巴巴的类图一个工厂类一个产品接口几个具体产品然后一句客户端不需要知道具体类名。看完觉得懂了合上书自己写又不知道从哪下手。我当年也是这样直到有一次被安排去改一个创建对象的逻辑代码里散落着七八处new不同实现类的地方每加一种类型就要全局搜索替换改得我头皮发麻才真正理解工厂模式到底在解决什么问题。这篇内容我打算用一个披萨店的场景把简单工厂模式从头到尾模拟一遍。选披萨店不是因为它多高级恰恰是因为它足够日常顾客点单、后厨制作、不同口味对应不同做法这套流程天然就带着根据条件创建不同对象的味道拿来理解工厂模式再合适不过。不管你是刚学完面向对象基础、想找个能跑起来的例子练手的新手还是写过一些业务代码、想回头把设计模式吃透的开发者都能从这篇里拿到可以直接复现的代码和踩坑经验。我会把为什么这么设计讲透而不是只丢一段代码让你抄。1. 为什么披萨店是理解简单工厂的好切口1.1 从顾客点单看对象创建的痛点先别急着写代码我们把披萨店的业务流程在脑子里过一遍。顾客走进店里说我要一份芝士披萨店员记下订单后厨拿到订单开始做。这里有个关键点顾客说的是芝士披萨这个名字而不是给我 new 一个 CheesePizza 对象。顾客不关心披萨是怎么揉面、怎么加料、烤多久的他只关心最后端上来的是不是他要的那个口味。如果把这个流程直接翻译成最朴素的代码很多人的第一版大概是这样写的public class PizzaStore { public Pizza orderPizza(String type) { Pizza pizza; if (cheese.equals(type)) { pizza new CheesePizza(); } else if (veggie.equals(type)) { pizza new VeggiePizza(); } else if (pepperoni.equals(type)) { pizza new PepperoniPizza(); } else { throw new IllegalArgumentException(没有这种披萨); } pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); return pizza; } }这段代码能跑功能也对但它埋了一个很深的雷。注意看orderPizza这个方法里同时干了两件事决定创建哪种披萨以及执行披萨的制作流程。这两件事的变化频率完全不一样。制作流程准备、烘烤、切块、装盒基本是稳定的但有哪些披萨种类是会变的——今天加个榴莲口味明天下架一个卖不动的每次变动你都得回到这个方法里改if-else。这就是典型的紧耦合。PizzaStore作为调用方被迫知道了所有具体披萨类的存在。一旦披萨种类膨胀到十几种这个if-else会变成一坨谁都不想碰的面条代码。更麻烦的是如果店里还有别的业务比如外卖系统、会员积分系统也需要创建披萨对象那这套if-else就得复制粘贴好几份改一处漏一处bug 就是这么来的。1.2 简单工厂的核心思路把变的部分抽出去简单工厂模式要干的事说白了就一句话把根据条件创建对象这段容易变的逻辑单独拎到一个类里去。这个被拎出来的类就是工厂它专门负责造披萨而PizzaStore只管卖披萨和走流程。打个生活化的比方。原来的写法相当于店长既要在前台接待顾客、又要跑后厨决定每份披萨怎么做简单工厂相当于店里专门设了一个后厨调度员店长把订单往调度员手里一塞说芝士的调度员负责把对应的披萨做出来交给店长。店长从此不用记每种披萨的配方来了新口味也只需要跟调度员说一声店长的接待流程一个字都不用改。这个调度员就是工厂类。它的职责边界非常清晰输入一个类型标识输出一个已经实例化的产品对象。至于产品内部怎么实现、有哪些子类调用方一概不关心。这种调用方只依赖抽象、不依赖具体的做法正是后面所有工厂模式工厂方法、抽象工厂的共同地基。简单工厂虽然不在 GoF 二十三种经典模式之列但它是最容易上手、也最能让人直观感受到解耦价值的一个所以我一直建议把它当作设计模式的入门第一课。2. 把披萨店拆成四层产品、具体产品、工厂、调用方2.1 产品抽象层Pizza 该定义哪些行为设计任何工厂模式第一步永远是先把产品抽象出来。抽象的意思是找出所有披萨共有的、稳定的行为把它们放进一个父类或接口里。披萨店里的每份披萨不管什么口味都要经历准备、烘烤、切块、装盒这四步这就是共性。public abstract class Pizza { protected String name; public void prepare() { System.out.println(准备 name); } public void bake() { System.out.println(烘烤 name); } public void cut() { System.out.println(切块 name); } public void box() { System.out.println(装盒 name); } public String getName() { return name; } }这里我用的是抽象类而不是接口原因值得说一下。这四步流程prepare/bake/cut/box对大多数披萨来说是通用的用抽象类可以把这些通用实现直接写死在父类里子类不用重复写同时子类还能按需重写某一步比如芝士披萨的prepare要额外加芝士重写一下就行。如果换成接口每个具体披萨都得把这四步全实现一遍代码重复度会很高。提示抽象类适合有共享实现 部分可定制的场景接口适合只约定契约、实现完全各异的场景。选哪个不是教条看共性多不多。当然如果你希望产品层更纯粹、更强调能力契约用接口也完全没问题只是通用流程得靠一个中间抽象类来承载。我个人的习惯是产品有明确的通用流程模板时优先抽象类纯粹的能力声明时用接口。2.2 具体产品层三种披萨各自的差异点有了抽象父类具体披萨就只需要继承它、设好名字、按需重写差异化的步骤。我准备了三种口味覆盖完全走默认流程和需要定制某一步两种情况public class CheesePizza extends Pizza { public CheesePizza() { this.name 芝士披萨; } Override public void prepare() { System.out.println(为芝士披萨铺上双层马苏里拉); } } public class VeggiePizza extends Pizza { public VeggiePizza() { this.name 蔬菜披萨; } } public class PepperoniPizza extends Pizza { public PepperoniPizza() { this.name 意式香肠披萨; } Override public void bake() { System.out.println(意式香肠披萨需要多烤两分钟至边缘焦脆); } }注意VeggiePizza什么都没重写直接沿用父类的四步流程——这正是抽象类带来的好处通用逻辑零重复。而CheesePizza重写了prepare、PepperoniPizza重写了bake体现了共性走父类、个性走子类的分工。这种结构让后续加新口味变得极其轻松新建一个类、设个名字需要定制就重写对应方法其他一概不动。2.3 工厂类一个方法收口所有创建逻辑现在到了主角。工厂类的设计要点是对外只暴露一个创建方法内部用条件判断决定实例化哪个具体类。所有new具体披萨的代码从此只存在于这一个地方。public class SimplePizzaFactory { public Pizza createPizza(String type) { Pizza pizza null; if (cheese.equals(type)) { pizza new CheesePizza(); } else if (veggie.equals(type)) { pizza new VeggiePizza(); } else if (pepperoni.equals(type)) { pizza new PepperoniPizza(); } return pizza; } }这里有个细节我特意没在工厂里抛异常而是返回null把类型不存在怎么办的决定权交给调用方。为什么因为工厂的职责是造东西它不该替调用方决定造不出来时业务该怎么反应——也许调用方想抛异常也许想返回一个默认披萨也许想记日志后走降级逻辑。工厂越笨、越只做一件事它的复用性就越好。当然如果你确定整个系统对未知类型的处理方式统一在工厂里直接抛IllegalArgumentException也完全合理这属于团队约定层面的取舍。2.4 调用方PizzaStore 终于清爽了改造后的PizzaStore通过组合的方式持有一个工厂实例orderPizza里再也不用出现任何具体披萨类名public class PizzaStore { private final SimplePizzaFactory factory; public PizzaStore(SimplePizzaFactory factory) { this.factory factory; } public Pizza orderPizza(String type) { Pizza pizza factory.createPizza(type); if (pizza null) { System.out.println(抱歉没有 type 这种披萨); return null; } pizza.prepare(); pizza.bake(); pizza.cut(); pizza.box(); return pizza; } }对比第一版变化非常明显PizzaStore现在只依赖Pizza抽象和SimplePizzaFactory两个东西具体有哪些披萨它完全不知道。加一种新口味只需要改工厂类PizzaStore一行都不用动。这就是简单工厂带来的单一职责和开闭原则的改善——虽然它还没做到完美加新口味仍要改工厂的if-else但已经把变化集中到了一个点上比原来散落各处强太多。3. 跑起来一次完整的点单流程与输出验证3.1 组装并运行主流程四层都齐了写个入口把整条链路串起来验证一下行为是否符合预期public class PizzaShopDemo { public static void main(String[] args) { SimplePizzaFactory factory new SimplePizzaFactory(); PizzaStore store new PizzaStore(factory); System.out.println( 顾客点了一份芝士披萨 ); store.orderPizza(cheese); System.out.println(\n 顾客点了一份意式香肠披萨 ); store.orderPizza(pepperoni); System.out.println(\n 顾客点了一份不存在的口味 ); store.orderPizza(durian); } }运行后你会看到类似这样的输出 顾客点了一份芝士披萨 为芝士披萨铺上双层马苏里拉 烘烤 芝士披萨 切块 芝士披萨 装盒 芝士披萨 顾客点了一份意式香肠披萨 准备 意式香肠披萨 意式香肠披萨需要多烤两分钟至边缘焦脆 切块 意式香肠披萨 装盒 意式香肠披萨 顾客点了一份不存在的口味 抱歉没有 durian 这种披萨从输出能清楚看到多态在起作用同样是调用pizza.prepare()芝士披萨走的是重写后的版本香肠披萨走的是父类默认版本。调用方PizzaStore完全不知道也不关心这些差异它只管按流程调用具体行为由实际创建出来的对象决定。这就是面向抽象编程最直观的体现。3.2 验证解耦效果加一种新口味要改几处光看输出还不够真正检验设计好坏的办法是加一个新需求看要动几个文件。假设现在要加一个榴莲披萨我们数一数改动点新建DurianPizza类继承Pizza设名字按需重写方法——新增 1 个文件在SimplePizzaFactory的createPizza里加一个else if分支——修改 1 处PizzaStore、PizzaShopDemo以及其他任何调用方——零改动。对比改造前那种每个调用方都复制一份 if-else的写法新口味可能要改三四个地方还容易漏。现在变化被牢牢锁在工厂这一个点上这就是解耦带来的实际收益。你可以自己动手加一个榴莲披萨试试感受一下这种只改一处的顺畅。注意简单工厂的开闭原则是不彻底的——加新产品仍要修改工厂类。这是它的固有局限不是实现错误。想彻底做到加产品不改工厂就得上工厂方法模式每个产品配一个工厂。简单工厂用改一处换来了结构简单这个取舍在大多数中小型场景里是划算的。4. 简单工厂的边界什么时候该用什么时候别硬套4.1 三个适合上简单工厂的信号不是所有new都值得包一层工厂。我总结了几个该用的信号你可以对照自己的代码看看创建逻辑带条件分支代码里出现if-else或switch根据类型new不同实现且这个分支在多处重复出现这就是最典型的适用场景。调用方不该知道具体类比如业务层只想拿到一个支付处理器消息发送器不关心底层是哪个实现这时候用工厂把具体类藏起来调用方只依赖抽象。产品种类会持续增加如果产品集合是封闭的、几乎不会变那直接new反而更简单但如果三天两头加新类型把创建逻辑收口到工厂能显著降低维护成本。反过来如果只是创建一个固定的、不会变的简单对象比如new ArrayList()、new Date()硬套工厂就是过度设计纯属给自己找麻烦。设计模式是解决问题的工具不是炫技的装饰。4.2 简单工厂 vs 工厂方法一张表看清取舍很多人学完简单工厂会问那工厂方法模式又是干嘛的两者最核心的区别在于谁来承担加新产品带来的修改。我用一张表对比一下对比维度简单工厂工厂方法工厂数量一个工厂类统管所有产品每个产品对应一个工厂类加新产品要改哪里修改工厂类的条件分支新增一个工厂类不改旧代码是否符合开闭原则不完全符合要改工厂符合对扩展开放结构复杂度低一个类搞定高类数量成倍增加适合场景产品种类少、变化不频繁产品族多、扩展频繁我的经验是产品种类在个位数、且短期内不会爆炸式增长时简单工厂是性价比最高的选择。它用一个类解决了 80% 的问题剩下的 20% 才需要动用更重的模式。上来就工厂方法、抽象工厂全套招呼往往会让一个本来简单的模块变得难以阅读。先简单工厂跑起来等真的感觉到每次加产品都要改工厂很烦了再重构升级这才是务实的演进路径。4.3 几个容易踩的实现坑最后分享几个我在实际写这类代码时踩过、也见别人踩过的坑都是些文档里不会写但很要命的细节坑一工厂方法返回null后调用方忘了判空。我在 2.3 节让工厂返回null调用方必须判空否则紧接着pizza.prepare()就是空指针。如果你团队里对空值处理没有强约定我更推荐工厂直接抛异常或者返回一个空对象一个什么都不做的默认披萨让调用方永远拿到一个可用对象。空对象模式在这里和简单工厂是绝配。坑二类型标识用魔法字符串。cheese、veggie这种字符串散落在各处拼错一个字母就是运行时才发现的 bug。稳妥做法是用枚举PizzaType.CHEESE或者常量类来收口编译期就能挡住拼写错误。如果非要用字符串至少把类型常量集中定义在一个地方。坑三工厂里塞了业务逻辑。有人图省事在createPizza里顺手做了库存扣减、日志记录、价格计算结果工厂越来越臃肿最后变成一个上帝类。记住工厂只干一件事——创建对象。创建之外的逻辑该放哪放哪别往里塞。坑四把工厂做成了静态工具类后无法替换。有些人喜欢把createPizza写成static方法调用处直接SimplePizzaFactory.createPizza(...)。这样确实省事但代价是无法通过继承或多态替换工厂实现也没法在测试里 mock 掉工厂。如果这个工厂未来可能需要换实现比如测试环境用假工厂就别用静态方法老老实实注入实例。5. 从披萨店再往前走一步把披萨店这个例子完整写一遍、跑一遍、再动手加个新口味你对简单工厂的理解就不会停留在背概念的层面了。它真正教会你的是三件事识别代码里变化的部分并把它隔离出来、让调用方依赖抽象而非具体类、用组合把职责拆给不同的类。这三点是后面所有设计模式共享的底层思维披萨店只是它们最朴素的一次演练。我个人在实际项目里的体会是简单工厂最大的价值不在于它本身多精妙而在于它是一个思维拐点——当你开始习惯性地问自己这段创建逻辑该不该抽出去调用方是不是知道了太多它不该知道的类你的代码结构就会自然而然地往好的方向走。至于要不要升级到工厂方法、抽象工厂等业务真的复杂到那个程度再说别为了模式而模式。如果你想让这个例子更完整可以试着给它加上订单队列和多门店共享工厂那时候你会自然遇到工厂该不该是单例不同门店要不要不同工厂这些新问题顺着这些问题往下走工厂方法模式的门就自己打开了。
延伸阅读

更多相关文章

2026/10/27 6:18:27

AI编程时代程序员的核心竞争力与进化路径

1. 当AI开始写代码:程序员职业的现状扫描2023年GitHub发布的开发者调查报告显示,92%的程序员已在工作中使用AI编程工具,其中35%的代码由AI辅助生成。这个数据背后是一个正在发生的行业变革:当Copilot能在300毫秒内补全整段业务逻辑…

2026/10/27 6:18:27

MySQL8增量备份实战:Xtrabackup8核心技术与优化

1. Xtrabackup8与MySQL8增量备份的核心价值在MySQL数据库运维领域,数据备份是保障业务连续性的最后防线。传统逻辑备份工具如mysqldump在面对TB级数据库时,恢复时间可能长达数小时甚至数天。而Percona XtraBackup作为业界公认的物理备份工具,…

2026/10/27 6:13:27

Win10 C盘空间清理全攻略:从原理到实战

1. 为什么Win10的C盘总是莫名其妙变红?每次看到C盘飘红,我的血压就跟着飙升。作为从Win95时代一路走来的老用户,我深知系统盘空间管理的痛点。Win10系统默认安装后,即使不装任何软件,C盘也会随着使用时间推移不断膨胀。…

2026/10/27 6:58:31

自监督Tab-Iforest与Django:构建智能异常检测系统

简介:一份基于自监督学习Tab-Iforest算法与Django框架的智能数据分析系统完整源码资源,面向需要实现无监督异常检测、Web后端开发及数据可视化展示的开发者与研究人员。系统集成数据采集预处理、Tab-Iforest异常检测模型、异常告警与响应机制、可视化交互…

2026/10/27 6:58:31

Nginx WebServer高并发调优与生产级配置实战

1. 项目概述:这不是一道题,而是一张Web服务的全栈能力图谱“WebServer -- 面试题(下)”这个标题乍看像一份普通的技术复习资料,但在我带过几十个后端开发岗面试、参与过上百次系统架构评审之后,我越来越确信:它根本不是…

2026/10/27 6:58:31

基于自监督学习与孤立森林的表格数据异常检测:Django 集成实践

简介:基于自监督学习Tab-Iforest异常检测算法与Django框架开发的智能数据分析系统完整项目,面向机器学习算法学习者、Django后端开发者及需要数据异常监测方案的技术人员,解决存储数据中异常点难以人工识别的实际问题,可作为课程设…

2026/10/27 6:58:31

东华OJ刷题攻略:每日3题高效备考法

1. 项目概述"东华复试OJ每日3题打卡复盘58~60"这个标题背后,反映的是一个典型的计算机专业考研复试备考场景。作为过来人,我深知OJ(Online Judge)刷题在计算机考研复试中的重要性。东华大学计算机专业的复试历来注重算法和编程能力的考察&…

2026/10/27 6:58:31

Swift析构机制与内存管理实践指南

1. Swift 析构机制深度解析在Swift开发中,内存管理是一个永恒的话题。作为自动引用计数(ARC)语言,Swift通过析构过程(Deinitialization)来释放不再需要的实例资源。不同于其他语言的"析构函数"概…

2026/10/27 6:53:31

AIGC技术解析:从生成模型到行业应用

1. AIGC技术全景解析:从基础原理到行业变革AIGC(AI Generated Content)正在重塑内容生产的游戏规则。作为一名长期跟踪AI技术落地的从业者,我亲眼见证了这项技术如何从实验室走向产业化。不同于传统的PGC(专业生成内容…

2026/10/26 0:20:20

全国计算机二级C语言真题PDF高效刷题指南

简介:这份PDF是全国计算机二级C语言考试的试题与答案解析,专为备考二级C语言的考生准备。内容涵盖C语言特点与设计理念、程序基本组成单位、main函数执行流程、函数嵌套调用规则、标识符命名规范、整型实型字符型数据类型、八进制十六进制数合法形式、运…

2026/10/26 1:53:47

requests+BeautifulSoup:爬虫抓取h1标题的入门实战指南

简介:一份以实战为导向的Python爬虫入门资源,面向具备基本编程基础、希望快速掌握网络数据抓取的初学者,可解决从零编写简单爬虫的常见困惑。实例以requests库发送HTTP请求、BeautifulSoup解析HTML文档,完整演示从网页中提取 标题…

2026/10/27 0:59:21

递推最小二乘参数辨识MATLAB编程:二阶系统与渐消记忆实现

简介:这是一篇面向控制系统、信号处理与机器学习等领域学习者的最小二乘参数辨识程序说明文档。资源共包含一个docx格式文档,压缩包大小七十八KB,内容围绕递推最小二乘算法和渐消记忆最小二乘算法展开:先构造一个极点位于单位圆内…

2026/10/27 0:02:28

WorkBuddy_新建任务与工作模式19_“模式选错”的典型代价

“模式选错”的典型代价 摘要 在软件工程中,设计模式、架构模式、并发模式乃至团队协作模式的选择,往往在项目早期就决定了后续的开发效率和维护成本。本文通过多个真实场景,剖析“模式选错”带来的典型代价——包括返工成本、技术债务累积、…

2026/10/27 0:02:28

WorkBuddy_新建任务与工作模式20_新手推荐路径

新手推荐路径 摘要 在 AI 辅助开发工具快速普及的今天,新手面对最多的困惑往往不是"工具够不够强",而是"我该从哪一步开始用"。本文提出一条经过实践验证的三阶段学习路径:先从 Ask(问答式交互)起…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑