当前位置: 首页 > article >正文

设计模式实战解读(二):工厂模式——对象创建的解耦艺术

本文是「设计模式实战解读」系列第二篇。系列文章统一按照定义 → 痛点场景 → 模式结构 → 核心实现 → 真实应用 → 常见变种 → 优缺点 → 避坑指南 → FAQ的结构展开每篇聚焦一个模式讲透。一句话定义工厂模式Factory把对象的创建逻辑从使用者中剥离出来由专门的工厂负责创建。调用方只关心我要什么不关心怎么造。归属创建型模式。一、没有工厂时的痛点假设你在做一个消息通知模块需要支持多种发送渠道publicvoidsendNotification(Stringchannel,Stringmessage){if(email.equals(channel)){EmailSendersendernewEmailSender(smtp.xxx.com,465,user,pwd);sender.send(message);}elseif(sms.equals(channel)){SmsSendersendernewSmsSender(https://sms-api.xxx.com,appKey,secret);sender.send(message);}elseif(dingtalk.equals(channel)){DingTalkSendersendernewDingTalkSender(webhook_url,sign_secret);sender.send(message);}elseif(wechat.equals(channel)){WeChatSendersendernewWeChatSender(corpId,agentId,secret);sender.send(message);}}问题违反开闭原则每新增一种渠道都要改这段代码创建逻辑散落各处多处需要创建 Sender重复的构造参数到处写调用方强耦合具体类具体类改了所有调用方都要改无法做单元测试直接 new 出来的对象没法被 mock二、模式结构工厂模式有三个层次层次 1: 简单工厂 Client → Factory.create(type) → ProductA / ProductB / ProductC 层次 2: 工厂方法 Client → AbstractFactory.create() → Product ↑ FactoryA → ProductA ↑ FactoryB → ProductB 层次 3: 抽象工厂 Client → AbstractFactory ├ createProductX() → X1 / X2 └ createProductY() → Y1 / Y2三个层次的适用边界简单工厂产品种类少10新增频率低工厂方法产品种类多经常新增要做到加新品不改旧代码抽象工厂需要创建一组相关联的产品如 UI 组件族按钮输入框下拉框三、核心实现3.1 简单工厂// 产品接口publicinterfaceMessageSender{voidsend(Stringmessage,Stringtarget);}// 具体产品publicclassEmailSenderimplementsMessageSender{/* ... */}publicclassSmsSenderimplementsMessageSender{/* ... */}publicclassDingTalkSenderimplementsMessageSender{/* ... */}// 简单工厂publicclassSenderFactory{privatestaticfinalMapString,SupplierMessageSenderCREATORSnewHashMap();static{CREATORS.put(email,EmailSender::new);CREATORS.put(sms,SmsSender::new);CREATORS.put(dingtalk,DingTalkSender::new);}publicstaticMessageSendercreate(Stringchannel){SupplierMessageSendercreatorCREATORS.get(channel);if(creatornull){thrownewIllegalArgumentException(Unsupported channel: channel);}returncreator.get();}}// 调用方解耦了MessageSendersenderSenderFactory.create(email);sender.send(Hello,testexample.com);用MapString, Supplier代替 if-else新增渠道只需往 Map 里注册不改工厂逻辑。3.2 工厂方法当产品创建逻辑差异大需要不同配置、不同依赖时每种产品对应一个工厂// 抽象工厂publicinterfaceSenderFactory{MessageSendercreate(SenderConfigconfig);}// 具体工厂publicclassEmailSenderFactoryimplementsSenderFactory{OverridepublicMessageSendercreate(SenderConfigconfig){// Email 特有的初始化SMTP 连接、SSL 证书加载SmtpClientsmtpnewSmtpClient(config.getHost(),config.getPort());smtp.auth(config.getUsername(),config.getPassword());returnnewEmailSender(smtp);}}publicclassDingTalkSenderFactoryimplementsSenderFactory{OverridepublicMessageSendercreate(SenderConfigconfig){// 钉钉特有的初始化签名计算、Token 缓存DingTalkClientclientnewDingTalkClient(config.getWebhook());client.setSignSecret(config.getSecret());returnnewDingTalkSender(client);}}调用方通过注入不同工厂来切换产品完全不耦合具体产品类。3.3 结合 Spring 的工厂注册在 Spring 项目中工厂模式最常见的落地方式是策略 自动注册// 每种渠道的 Sender 标注自己的类型Component(email)publicclassEmailSenderimplementsMessageSender{/* ... */}Component(sms)publicclassSmsSenderimplementsMessageSender{/* ... */}Component(dingtalk)publicclassDingTalkSenderimplementsMessageSender{/* ... */}// 工厂利用 Spring 自动收集所有实现ComponentpublicclassSenderFactory{privatefinalMapString,MessageSendersenderMap;// Spring 会自动把所有 MessageSender 按 Bean Name 注入到 MappublicSenderFactory(MapString,MessageSendersenderMap){this.senderMapsenderMap;}publicMessageSendergetSender(Stringchannel){MessageSendersendersenderMap.get(channel);if(sendernull){thrownewIllegalArgumentException(No sender for: channel);}returnsender;}}这是实际项目中用得最多的模式——零手动注册新增一个 Component 就自动生效。四、真实应用场景4.1 框架级应用框架工厂在哪创建什么SpringBeanFactoryBean 实例MyBatisSqlSessionFactorySqlSessionSLF4JLoggerFactoryLoggerJDBCDriverManagerConnectionJacksonObjectMapper (builder)Serializer/DeserializerNettyChannelFactoryChannel4.2 业务场景业务工厂产品为什么用工厂消息通知SenderFactory邮件/短信/钉钉/飞书渠道多、各自初始化逻辑不同支付PaymentFactory微信/支付宝/银联支付方式差异大策略隔离文件解析ParserFactoryExcel/CSV/JSON/XML文件格式各自一套解析逻辑连接器ConnectorFactoryHTTP/RPC/MQ/DB协议不同连接方式差异大报告导出ExporterFactoryPDF/Word/HTML渲染逻辑完全不同规则引擎ConditionFactory等于/包含/大于/正则条件类型多各自求值逻辑独立4.3 iPaaS 连接器场景在 iPaaS 平台中连接器Connector是工厂模式的经典落地用户配置的流程节点 → 指定了 connectorType dingtalk ↓ ConnectorFactory.create(dingtalk) ↓ DingTalkConnector已初始化好 Token 缓存、签名算法连接器的创建涉及 OAuth 初始化、Token 刷新、签名算法加载、SDK 实例化等重逻辑不可能让业务代码直接 new。工厂把这些复杂性封装起来调用方只需factory.create(type)拿到一个可用的连接器实例。五、常见变种5.1 静态工厂方法不新建工厂类在产品类上加静态方法publicclassConnection{privateConnection(Stringurl){/* ... */}// 静态工厂方法命名比 new 更有语义publicstaticConnectionof(Stringurl){returnnewConnection(url);}publicstaticConnectionpooled(Stringurl,intpoolSize){/* ... */}publicstaticConnectionreadonly(Stringurl){/* ... */}}JDK 中大量使用List.of()、Optional.of()、Integer.valueOf()、Collections.emptyList()。优势方法名有语义 可以返回缓存实例 可以返回子类型。5.2 配置驱动工厂产品类型由配置文件/数据库决定工厂在运行时动态加载// 从配置中读取所有渠道 → 类名映射// channel.emailcom.xxx.EmailSender// channel.smscom.xxx.SmsSenderpublicclassDynamicFactory{privatefinalMapString,Class?registrynewHashMap();publicvoidregister(Stringtype,StringclassName)throwsClassNotFoundException{registry.put(type,Class.forName(className));}publicMessageSendercreate(Stringtype)throwsException{return(MessageSender)registry.get(type).getDeclaredConstructor().newInstance();}}这种方式配合 SPIServiceLoader可以做到不改代码、加 jar 包就能新增产品。5.3 延迟创建工厂有些产品创建成本高工厂不立即创建而是返回一个工厂引用publicclassLazyFactory{publicSupplierHeavyObjectcreateLazy(Configconfig){// 不立即创建返回一个 Supplierreturn()-newHeavyObject(config);}}// 使用方在真正需要时才调用 .get()SupplierHeavyObjectlazyfactory.createLazy(config);// ... 很多逻辑 ...HeavyObjectobjlazy.get();// 这时才真正创建六、优缺点优点缺点解耦创建逻辑和使用逻辑增加类的数量每种产品一个类/工厂符合开闭原则新增产品不改工厂简单场景过度使用反而增加复杂度集中管理创建逻辑修改一处生效全局产品需要遵循统一接口有时会过度抽象方便做缓存、池化、日志统计调用方丧失了对象初始化的细粒度控制对单元测试友好可以注入 mock 工厂—七、避坑指南坑 1简单工厂退化成巨型 if-else用MapString, Supplier或 Spring 自动注入代替 if-else。如果仍然用 if-else每次加渠道都改工厂类开闭原则形同虚设。坑 2工厂方法过度使用如果产品只有 2-3 种且不太会增加直接用简单工厂就够了。为 3 种产品建 3 个工厂类 1 个抽象工厂属于过度工程化。坑 3工厂和产品的依赖注入冲突在 Spring 项目中如果产品本身已经被 Spring 管理Component那用MapString, T自动注入比手写工厂更简洁。别自己 new 产品再放进 Map——让 Spring 帮你干。坑 4工厂创建的对象未做生命周期管理工厂创建了对象但没人负责关闭/销毁——连接泄漏、线程泄漏。工厂如果创建有资源的对象连接、流应该同时提供destroy()或配合连接池使用。坑 5产品接口过度抽象为了统一把完全不同的东西强行抽象成一个接口。比如 EmailSender 和 WebhookSender 的参数完全不同邮件有收件人/抄送/附件Webhook 只有 URL非要统一成send(String msg)会导致各种 hack。解法允许产品接口有泛型参数或者把差异部分放到 Config 对象里。八、常见问题FAQQ简单工厂、工厂方法、抽象工厂什么时候用哪个A大部分业务场景简单工厂就够了。只有当满足以下条件时才升级产品种类多10且频繁新增 → 用工厂方法需要创建一组相关联的产品族 → 用抽象工厂。90% 的实际项目只需要简单工厂。Q工厂模式和策略模式有什么区别A工厂模式关注的是创建——如何构造一个对象。策略模式关注的是行为——如何选择一种算法来执行。很多时候二者结合使用工厂创建出策略对象调用方使用策略对象。区别在于侧重点不同。QSpring 的 BeanFactory 是不是工厂模式A是的而且是工厂模式最经典的工业级实现。它不仅创建 Bean还管理 Bean 的生命周期初始化、销毁、作用域singleton/prototype/request、依赖注入、AOP 代理。可以理解为超级工厂。Q什么时候不应该用工厂A对象创建逻辑简单直接 new 就行、没有依赖、产品不需要面向接口编程、不需要 mock 测试——这些场景直接 new 比引入工厂更简洁。不要为了设计模式而设计模式。Q工厂创建的对象应该是单例还是每次 newA取决于产品是否有状态。无状态的服务类如 Sender可以缓存复用有状态的实例如正在使用的 Connection每次创建。通常在工厂内加一层缓存判断即可。九、小结工厂模式的核心价值只有一句话把 “new” 的权利从调用方收回来交给专门的角色管理。记住三个实践要点Spring 项目优先用MapString, T自动注入零代码实现工厂效果简单工厂用 Map Supplier 代替 if-else满足开闭原则工厂创建的对象别忘记生命周期管理——谁创建谁负责销毁下一篇我们聊模板方法模式——当一系列操作有固定的骨架但细节各不相同时如何做到复用骨架、开放细节。标签#设计模式 #工厂模式 #Factory #简单工厂 #工厂方法 #抽象工厂 #Java #Spring #创建型模式 #策略模式 #依赖注入 #面向对象 #软件工程

相关文章:

设计模式实战解读(二):工厂模式——对象创建的解耦艺术

本文是「设计模式实战解读」系列第二篇。系列文章统一按照 定义 → 痛点场景 → 模式结构 → 核心实现 → 真实应用 → 常见变种 → 优缺点 → 避坑指南 → FAQ 的结构展开,每篇聚焦一个模式讲透。 一句话定义 工厂模式(Factory)&#xff1a…...

Windows上安装APK文件的终极指南:告别臃肿模拟器,轻松实现跨平台应用安装

Windows上安装APK文件的终极指南:告别臃肿模拟器,轻松实现跨平台应用安装 【免费下载链接】APK-Installer An Android Application Installer for Windows 项目地址: https://gitcode.com/GitHub_Trending/ap/APK-Installer 你知道吗?…...

Qwen模型 LeetCode 2608. 图中的最短环 Java实现

哎呀,2608. 图中的最短环!这题可有意思了~我第一次做时也卡了好一会儿,后来发现用 **BFS 枚举每条边 临时删除** 的思路特别清爽!### 🌟 核心思想: - 对于每一条边 (u, v),我们**暂…...

Qwen模型 LeetCode 2603. 收集树中金币 Python3实现

当然可以!这次我给你一个**更精炼、更 Pythonic、逻辑清晰又高效**的 Python3 实现,还附带了详细的思路说明和小技巧~✨这道题的本质是:**在树上剪枝两次后,剩下的边数 2 就是答案**。为什么?因为&#xf…...

Qwen模型 LeetCode 2603. 收集树中金币 Java实现

哎呀,这道题我可太熟啦!2603. 收集树中金币,看着挺复杂的,其实想通了就特别有意思~让我跟你聊聊我的思路~这题本质上是个树形DP问题,我们需要在无向树上进行两次遍历。先说说我的理解哈&#xf…...

矩阵补全因果推断:破解贸易政策评估中的内生性与异质性难题

1. 项目概述:当因果推断遇上贸易政策评估的“硬骨头”做贸易政策评估的同行都知道,这事儿有多棘手。你想啊,一个国家签了个自由贸易协定(FTA),几年后出口额涨了,你怎么知道这增长里有多少是协定…...

Web安全 - 国密 SSL / TLCP 接入手把手系列

文章目录这个系列覆盖什么系列目录本系列和已有文档的关系权威资料入口这是一套面向“从来没接过国密 SSL”的开发者写的系列博客。它以本工程 gm-ssl-client 为主线,先补齐 SSL/TLS、国密算法、TLCP、证书、Java Provider 等基础知识,再一步一步讲清楚如…...

为什么92%的AI生成BP被秒拒?ChatGPT商业计划书写作的5大合规红线,今天不看明天就踩坑

更多请点击: https://kaifayun.com 第一章:AI商业计划书被秒拒的底层逻辑真相 投资人不是拒绝AI,而是拒绝“伪AI叙事”。当一份计划书开篇即堆砌“大模型”“AIGC”“多模态融合”等术语,却未定义清晰的用户痛点、可验证的最小闭…...

井下巷道无感精准定位 作业人员在岗离岗智能甄别

井下巷道无感精准定位 作业人员在岗离岗智能甄别技术白皮书编制单位:镜像视界浙江科技有限公司技术资质:国家十四五重点课题研究、镜像视界浙江普陀时空大数据应用技术联合研究、河南省电检院权威认证版本:V1.0一、项目概述井下巷道空间迂回交…...

【ChatGPT小红书爆款文案公式】:20年AI内容专家亲授3步生成高互动率文案(附17个真实转化数据)

更多请点击: https://kaifayun.com 第一章:ChatGPT小红书爆款文案的核心认知 小红书平台的内容生态高度依赖「真实感」「强共鸣」与「高信息密度」三重底层逻辑。ChatGPT生成的文案若直接照搬通用模板,极易因缺乏人设锚点、场景细节和情绪节…...

DeepSeek LeetCode 2617. 网格图中最少访问的格子数 TypeScript实现

以下是 LeetCode 2617「网格图中最少访问的格子数」的 TypeScript 实现,采用 BFS 并查集(路径压缩) 优化,保证每个格子只被访问一次,时间复杂度 O(mnα(mn)),空间复杂度 O(mn)。typescript function minim…...

DeepSeek LeetCode 2617. 网格图中最少访问的格子数 Java实现

以下是 LeetCode 2617「网格图中最少访问的格子数」的 Java 实现,采用 BFS TreeSet 优化,保证每个格子只被访问一次,时间复杂度 O(mn log(mn))。java class Solution {public int minimumVisitedCells(int[][] grid) {int m grid.length, n…...

Veo视频生成引擎深度集成方案(官方未公开的Webhook级联协议与跨平台帧同步技术首次披露)

更多请点击: https://kaifayun.com 第一章:Veo与其他AI视频工具整合 Veo 作为 Google 推出的高保真视频生成模型,其核心价值不仅体现在单点生成能力上,更在于与现有 AI 视频工作流的深度协同。它不追求封闭生态,而是通…...

【DeepSeek边缘部署实战指南】:20年架构师亲授5大避坑法则与3步极简上线法

更多请点击: https://codechina.net 第一章:DeepSeek边缘部署的演进逻辑与核心挑战 随着大模型从云端向终端下沉,DeepSeek系列模型在边缘侧的部署正经历从“能跑”到“稳跑”、从“单点适配”到“全栈协同”的范式跃迁。这一演进并非单纯的技…...

3分钟上手Translumo:免费实时屏幕翻译工具终极指南

3分钟上手Translumo:免费实时屏幕翻译工具终极指南 【免费下载链接】Translumo Advanced real-time screen translator for games, hardcoded subtitles in videos, static text and etc. 项目地址: https://gitcode.com/gh_mirrors/tr/Translumo 你是否在游…...

Windows和Office一键激活终极指南:KMS_VL_ALL_AIO智能脚本完全解析

Windows和Office一键激活终极指南:KMS_VL_ALL_AIO智能脚本完全解析 【免费下载链接】KMS_VL_ALL_AIO Smart Activation Script 项目地址: https://gitcode.com/gh_mirrors/km/KMS_VL_ALL_AIO 还在为Windows系统激活和Office办公软件激活而烦恼吗?…...

如何在3分钟内精准定位Windows热键冲突:Hotkey Detective终极指南

如何在3分钟内精准定位Windows热键冲突:Hotkey Detective终极指南 【免费下载链接】hotkey-detective A small program for investigating stolen key combinations under Windows 7 and later. 项目地址: https://gitcode.com/gh_mirrors/ho/hotkey-detective …...

LangGraph 状态存储优化:处理大规模多智能体数据的高效方案

LangGraph 状态存储优化:处理大规模多智能体数据的高效方案 本文面向有LangGraph开发经验、需要落地大规模多智能体应用的开发者,从底层原理、架构设计到代码实现全方位讲解如何将LangGraph状态存储的性能提升10倍、成本降低80%,支撑10万+级多智能体并发运行。 引言 痛点引…...

贝叶斯网络中条件独立性的判断 CS188 Note13 学习笔记

更好的阅读体验 D-Separation D-separation 是贝叶斯网络中的一个概念,用于通过图结构DAG随机变量之间的条件独立性 首先需要回顾一下的是:在图中,只要给定了某个节点的所有父节点,那么该节点就与其所有祖先节点在逻辑上是相互独…...

贝叶斯网络基本概念 CS188 Note12 学习笔记

更好的阅读体验 问题引入 在Note11中我们提及到了联合分布,我们先要想的就是一个问题:如果我们有n个变量,每个变量有d种取值,那联合概率表一共需要dnd^ndn行,这是一个非常庞大的数据量,这时候就引入了贝叶斯网络。贝…...

如何用TestDisk和PhotoRec拯救丢失数据:3分钟快速诊断与完整恢复指南

如何用TestDisk和PhotoRec拯救丢失数据:3分钟快速诊断与完整恢复指南 【免费下载链接】testdisk TestDisk & PhotoRec 项目地址: https://gitcode.com/gh_mirrors/te/testdisk 数据丢失是每个计算机用户都可能遇到的噩梦场景,但幸运的是&…...

VideoSrt终极指南:3步实现视频自动字幕生成,告别手动打轴烦恼

VideoSrt终极指南:3步实现视频自动字幕生成,告别手动打轴烦恼 【免费下载链接】video-srt-windows 这是一个可以识别视频语音自动生成字幕SRT文件的开源 Windows-GUI 软件工具。 项目地址: https://gitcode.com/gh_mirrors/vi/video-srt-windows …...

亮度与色度:揭秘视觉世界的“双重密码“

一、一个让我"开窍"的画廊故事 几年前我去参观一个摄影展,展览的主题很特别——“同一个世界,两种讲述”。展厅被一道墙分成两半,左边墙上挂的全是黑白摄影作品,右边墙上挂的全是彩色摄影作品。最有意思的是&#xff0c…...

黑白电视的“单眼魔法“:揭秘那个只用亮度讲故事的奇妙世界

一、一个让我"开窍"的雪天故事 我记得小时候有一年冬天,老家下了一场特别大的雪。早晨拉开窗帘的瞬间,我整个人都呆住了——外面的世界变成了一片纯白,屋顶、树枝、田野、远山,全都被雪覆盖。所有的颜色都消失了&#x…...

CD-GraB算法:协调数据顺序,加速分布式机器学习收敛

1. 分布式机器学习中的收敛瓶颈与数据顺序的隐秘关联在分布式机器学习的世界里,我们每天都在和数据、算力、时间赛跑。当你把训练任务拆分到多个GPU或服务器节点上并行执行时,一个看似不起眼的问题往往会成为性能提升的“暗礁”:数据以什么顺…...

为什么92.7%的用户装错ChatGPT桌面版?——20年IT架构师亲测:3个隐藏配置项决定响应速度与上下文留存能力

更多请点击: https://codechina.net 第一章:ChatGPT桌面版下载安装 OpenAI 官方尚未发布官方支持的 ChatGPT 桌面应用程序(截至 2024 年底),但社区提供了稳定、安全且功能完整的开源桌面客户端,其中 Chat…...

[开源] 康复处方安全卫士:面向康复科与临床药学的处方前置风险拦截系统

本项目是专为康复医学场景设计的处方安全校验工具,对接医院信息系统(HIS)中的康复理疗处方流程,在医生提交前实时识别禁忌证与物理因子之间的互斥风险。核心机制由两部分构成:一是基于 YAML 定义的「禁忌证物理因子」互…...

[开源] 急诊分诊能力闯关训练系统:面向护士与临床教学的可视化季票式技能成长平台

本项目是专为急诊科护士、进修生及实习生设计的分诊判断力训练工具,以「病例闯关 季票进度 多维反馈」为核心机制,将抽象的分诊能力拆解为20个难度递进的实战关卡。我们不做泛泛而谈的题库,而是用时间压力、星级评价、连胜激励和薄弱点定位…...

[开源] 临床路径卡牌化培训系统:面向医保办与临床科室的交互式规则教学工具

本项目是临床路径卡牌化培训系统(Pathway-Deck),专为医院医保办工作人员、临床科室教学负责人及新入职医师设计,将卫健委临床路径、DRG/DIP支付规则、医保负面清单等确定性规范,转化为可拖拽、可构筑、可验证的视觉化卡…...

ctf show web入门 254

这是一道典型的php对象序列化的题目可以从代码看出,本题需要让$user->isvip为true就可以调用yiponekeygetflag()函数从而获取flag从这可以看出$this->username$u&&$this->password$p时isvip为true,所以我们尝试构造payload为&#xff1…...