【设计模式之美】【建造型】工厂模式:通过面向接口编程思路,串起业务流程
文章目录
- 一. 简单工厂(Simple Factory)
- 第一种简单工厂:面向接口编程与工厂类:划分功能职责
- 第二种:单例+简单工厂:节省内存和对象创建的时间
- 二. 工厂方法(Factory Method):进一步抽象:通过面向接口的思路创建对象
- 三. 什么时候使用工厂方法
- 四. 抽象工厂
一般情况下,工厂模式分为三种更加细分的类型:简单工厂、工厂方法和抽象工厂。
什么时候该用工厂模式?相对于直接 new 来创建对象,用工厂模式来创建究竟有什么好处呢?这是本文将要讨论的事情。
一. 简单工厂(Simple Factory)
第一种简单工厂:面向接口编程与工厂类:划分功能职责
如下代码流程描述了:根据不同的文件后缀(面向接口编程)创建不同的对象,然后执行相同方法的不同逻辑。
public class RuleConfigSource {public RuleConfig load(String ruleConfigFilePath) {//1. 获取文件后缀String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath);//这个是函数式接口,有一个抽象方法parse。//2. 根据不同的文件后缀,创建不同的parse对象(面向接口编程)IRuleConfigParser parser = null;if ("json".equalsIgnoreCase(ruleConfigFileExtension)) {parser = new JsonRuleConfigParser();} else if ("xml".equalsIgnoreCase(ruleConfigFileExtension)) {parser = new XmlRuleConfigParser();} else if ("yaml".equalsIgnoreCase(ruleConfigFileExtension)) {parser = new YamlRuleConfigParser();} else if ("properties".equalsIgnoreCase(ruleConfigFileExtension)) {parser = new PropertiesRuleConfigParser();} else {throw new InvalidRuleConfigException("Rule config file format is not supported: " + ruleConfigFilePath);}//3. 根据具体实现解析文本String configText = "";//从ruleConfigFilePath文件中读取配置文本到configText中RuleConfig ruleConfig = parser.parse(configText);return ruleConfig;}private String getFileExtension(String filePath) {//...解析文件名获取扩展名,比如rule.json,返回jsonreturn "json";}
}
为了增加代码的可读性:将功能独立的代码封装成函数:将创建对象的逻辑抽取出来
public RuleConfig load(String ruleConfigFilePath) {String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath);//1. 创建对象IRuleConfigParser parser = createParser(ruleConfigFileExtension);//check parser=?nullString configText = "";//从ruleConfigFilePath文件中读取配置文本到configText中//2. 执行指定对象的parse方法(面向接口编程)RuleConfig ruleConfig = parser.parse(configText);return ruleConfig;}private String getFileExtension(String filePath) {//...解析文件名获取扩展名,比如rule.json,返回jsonreturn "json";}private IRuleConfigParser createParser(String configFormat) {IRuleConfigParser parser = null;if ("json".equalsIgnoreCase(configFormat)) {parser = new JsonRuleConfigParser();} else if ("xml".equalsIgnoreCase(configFormat)) {parser = new XmlRuleConfigParser();} else if ("yaml".equalsIgnoreCase(configFormat)) {parser = new YamlRuleConfigParser();} else if ("properties".equalsIgnoreCase(configFormat)) {parser = new PropertiesRuleConfigParser();}return parser;}
}
类责任单一:对象创建的方法放到独立一个类中,具体地,
将 createParser() 函数剥离到一个独立的类中,让这个类只负责对象的创建。
...public class RuleConfigParserFactory {public static IRuleConfigParser createParser(String configFormat) {IRuleConfigParser parser = null;if ("json".equalsIgnoreCase(configFormat)) {parser = new JsonRuleConfigParser();} else if ("xml".equalsIgnoreCase(configFormat)) {parser = new XmlRuleConfigParser();} else if ("yaml".equalsIgnoreCase(configFormat)) {parser = new YamlRuleConfigParser();} else if ("properties".equalsIgnoreCase(configFormat)) {parser = new PropertiesRuleConfigParser();}return parser;}
}
类名与方法名:
- 大部分工厂类都是以“Factory”这个单词结尾的,但也不是必须的,比如 Java 中的 DateFormat、Calender。
- 工厂类中创建对象的方法一般都是 create 开头,比如代码中的 createParser(),但有的也命名为 getInstance()、createInstance()、newInstance(),有的甚至命名为 valueOf()(比如 Java String 类的 valueOf() 函数)等等,这个我们根据具体的场景和习惯来命名就好。
面向接口编程
面向接口编程(Interface-Oriented Programming)其核心思想是依赖于接口(或抽象类)来编写程序,而不是依赖于具体的实现类。具体地,通过接口来串联起业务代码的逻辑,而不是具体的实现类。
面向接口有如下好处:
- 松耦合:各个模块之间的依赖关系更松散。这使得系统中的组件可以更独立地开发、测试和部署
- 可扩展性:新的功能可以通过实现现有接口来扩展系统,而不会影响到已有的代码。
- 增强代码复用:通过面向接口编程,可以提高代码的可重用性。不同的实现类可以实现相同的接口,使得同一份代码可以适应不同的实际需求。
- 测试和调试:面向接口编程使得单元测试更加容易,可以使用模拟对象(mock objects)来模拟接口的行为,从而进行更有效的单元测试和调试。
第二种:单例+简单工厂:节省内存和对象创建的时间
为了节省内存和对象创建的时间,我们可以将 parser 事先创建好缓存起来。当调用 createParser() 函数的时候,我们从缓存中取出 parser 对象直接使用。
public class RuleConfigParserFactory {private static final Map<String, RuleConfigParser> cachedParsers = new HashMap<>();static {cachedParsers.put("json", new JsonRuleConfigParser());cachedParsers.put("xml", new XmlRuleConfigParser());cachedParsers.put("yaml", new YamlRuleConfigParser());cachedParsers.put("properties", new PropertiesRuleConfigParser());}public static IRuleConfigParser createParser(String configFormat) {if (configFormat == null || configFormat.isEmpty()) {return null;//返回null还是IllegalArgumentException全凭你自己说了算}//也去掉了if elseIRuleConfigParser parser = cachedParsers.get(configFormat.toLowerCase());return parser;}
}
对于上面两种简单工厂模式的实现方法,如果我们要添加新的 parser,那势必要改动到 RuleConfigParserFactory 的代码,那这是不是违反开闭原则呢?实际上,如果不是需要频繁地添加新的 parser,只是偶尔修改一下 RuleConfigParserFactory 代码,稍微不符合开闭原则,也是完全可以接受的。
总结一下,尽管简单工厂模式的代码实现中,有多处 if 分支判断逻辑,违背开闭原则,但权衡扩展性和可读性,这样的代码实现在大多数情况下(比如,不需要频繁地添加 parser,也没有太多的 parser)是没有问题的。
二. 工厂方法(Factory Method):进一步抽象:通过面向接口的思路创建对象
如果我们非得要将 if 分支逻辑去掉,那该怎么办呢?比较经典处理方法就是利用多态。按照多态的实现思路,对上面的代码进行重构。
如下我们新增一个创建对象的工厂,这样当我们新增一种 parser 的时候,只需要新增一个实现了 IRuleConfigParserFactory 接口的 Factory 类即可。所以,工厂方法模式比起简单工厂模式更加符合开闭原则。
public interface IRuleConfigParserFactory {IRuleConfigParser createParser();
}public class JsonRuleConfigParserFactory implements IRuleConfigParserFactory {@Overridepublic IRuleConfigParser createParser() {return new JsonRuleConfigParser();}
}XmlRuleConfigParserFactory
YamlRuleConfigParserFactory
...-------------public class RuleConfigSource {public RuleConfig load(String ruleConfigFilePath) {String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath);IRuleConfigParserFactory parserFactory = null;if ("json".equalsIgnoreCase(ruleConfigFileExtension)) {parserFactory = new JsonRuleConfigParserFactory();} else if ("xml".equalsIgnoreCase(ruleConfigFileExtension)) {parserFactory = new XmlRuleConfigParserFactory();} else if ("yaml".equalsIgnoreCase(ruleConfigFileExtension)) {parserFactory = new YamlRuleConfigParserFactory();} else if ("properties".equalsIgnoreCase(ruleConfigFileExtension)) {parserFactory = new PropertiesRuleConfigParserFactory();} else {throw new InvalidRuleConfigException("Rule config file format is not supported: " + ruleConfigFilePath);}// 这里是工厂方法的关键思路点:通过面向接口的思路来创建对象IRuleConfigParser parser = parserFactory.createParser();String configText = "";RuleConfig ruleConfig = parser.parse(configText);return ruleConfig;}}
但注意代码的复杂度此时增加了
工厂类对象的创建逻辑又耦合进了 load() 函数中,跟我们最初的代码版本非常相似,引入工厂方法非但没有解决问题,反倒让设计变得更加复杂了。
我们为工厂类再创建一个简单工厂,也就是工厂的工厂,用来创建工厂类对象。
public class RuleConfigSource {public RuleConfig load(String ruleConfigFilePath) {String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath);IRuleConfigParserFactory parserFactory = RuleConfigParserFactoryMap.getParserFactory(ruleConfigFileExtension);if (parserFactory == null) {throw new InvalidRuleConfigException("Rule config file format is not supported: " + ruleConfigFilePath);}IRuleConfigParser parser = parserFactory.createParser();String configText = "";//从ruleConfigFilePath文件中读取配置文本到configText中RuleConfig ruleConfig = parser.parse(configText);return ruleConfig;}private String getFileExtension(String filePath) {//...解析文件名获取扩展名,比如rule.json,返回jsonreturn "json";}
}//因为工厂类只包含方法,不包含成员变量,完全可以复用,
//不需要每次都创建新的工厂类对象,所以,简单工厂模式的第二种实现思路更加合适。
public class RuleConfigParserFactoryMap { //工厂的工厂private static final Map<String, IRuleConfigParserFactory> cachedFactories = new HashMap<>();static {cachedFactories.put("json", new JsonRuleConfigParserFactory());cachedFactories.put("xml", new XmlRuleConfigParserFactory());cachedFactories.put("yaml", new YamlRuleConfigParserFactory());cachedFactories.put("properties", new PropertiesRuleConfigParserFactory());}public static IRuleConfigParserFactory getParserFactory(String type) {if (type == null || type.isEmpty()) {return null;}IRuleConfigParserFactory parserFactory = cachedFactories.get(type.toLowerCase());return parserFactory;}
}
当我们需要添加新的规则配置解析器的时候,我们只需要创建新的 parser 类和 parser factory 类,并且在 RuleConfigParserFactoryMap 类中,将新的 parser factory 对象添加到 cachedFactories 中即可。代码的改动非常少,基本上符合开闭原则。
三. 什么时候使用工厂方法
实际上,对于规则配置文件解析这个应用场景来说,工厂模式需要额外创建诸多 Factory 类,也会增加代码的复杂性,而且,每个 Factory 类只是做简单的 new 操作,功能非常单薄(只有一行代码),也没必要设计成独立的类,所以,在这个应用场景下,简单工厂模式简单好用,比工厂方法模式更加合适。
那什么时候该用工厂方法模式,而非简单工厂模式呢?
- 如果代码块本身并不复杂,就几行代码而已,我们完全没必要将它拆分成单独的函数或者类。
- 当对象的创建逻辑比较复杂,不只是简单的 new 一下就可以,而是要组合其他类对象,做各种初始化操作的时候,我们推荐使用工厂方法模式,将复杂的创建逻辑拆分到多个工厂类中,让每个工厂类都不至于过于复杂。比如:不同jdbc数据源的管理、flink connector的管理。
四. 抽象工厂
抽象工厂模式的应用场景比较特殊,没有前两种常用。
在简单工厂和工厂方法中,类只有一种分类方式。当有多个类的分类时,我们可以让一个工厂负责创建多个不同类型的对象,而不是只创建一种 parser 对象。如下:
public interface IConfigParserFactory {IRuleConfigParser createRuleParser();ISystemConfigParser createSystemParser();//此处可以扩展新的parser类型,比如IBizConfigParser
}public class JsonConfigParserFactory implements IConfigParserFactory {@Overridepublic IRuleConfigParser createRuleParser() {return new JsonRuleConfigParser();}@Overridepublic ISystemConfigParser createSystemParser() {return new JsonSystemConfigParser();}
}...
XmlConfigParserFactory
YamlConfigParserFactory
PropertiesConfigParserFactory
参考:
王争:《设计模式之美》
相关文章:
【设计模式之美】【建造型】工厂模式:通过面向接口编程思路,串起业务流程
文章目录 一. 简单工厂(Simple Factory)第一种简单工厂:面向接口编程与工厂类:划分功能职责第二种:单例简单工厂:节省内存和对象创建的时间 二. 工厂方法(Factory Method)࿱…...
AI算法19-偏最小二乘法回归算法Partial Least Squares Regression | PLS
偏最小二乘法回归算法简介 算法概述 偏最小二乘法模型可分为偏最小二乘回归模型和偏最小二乘路径模型。其中偏最小二乘回归模型是一种新型的多元统计方法,它集中了主成分分析、典型相关分析和线性回归的特点,特别在解决回归中的共线性问题具有无可比拟…...
live555关于RTSP协议交互流程
RTP在和h264 RTP在和h265 RTP载荷AAC live555关于RTSP协议交互流程 live555的核心数据结构值之闭环双向链表 live555 rtsp服务器实战之createNewStreamSource 概要 rtsp在交互的过程中用到很多协议:tcp,udp,rtp,rtcp,sdp等协议;该篇文章主要分析在live555中这些…...
Centos7 安装私有 Gitlab
在 CentOS 7上,下面的命令也会在系统防火墙中打开 HTTP、HTTPS 和 SSH 访问。这是一个可选步骤,如果您打算仅从本地网络访问极狐GitLab,则可以跳过它。 sudo yum install -y curl policycoreutils-python openssh-server perl sudo systemct…...
浅谈数学模型在UGC/AIGC游戏数值配置调参中的应用(AI智能体)
浅谈数学模型在UGC/AIGC游戏数值配置调参中的应用 ygluu 卢益贵 关键词:UGC、AIGC、AI智能体、大模型、数学模型、游戏数值调参、游戏策划 一、前言 在策划大大群提出《游戏工厂:AI(AIGC/ChatGPT)与流程式游戏开发》讨论之后就…...
第T5周:使用TensorFlow实现运动鞋品牌识别
🍨 本文为🔗365天深度学习训练营 中的学习记录博客🍖 原作者:K同学啊 文章目录 一、前期工作1.设置GPU(如果使用的是CPU可以忽略这步)2. 导入数据3. 查看数据 二、数据预处理1、加载数据2、数据可视化3、再…...
网络编程学习之tcp
按下*(星号)可以搜索当前光标下的单词。 Tcp编程的过程 打开网络设备 Bind:给服务地址把ip号和端口号连接进去 Tcp是有状态的 Listen是进入监听状态,看有没有客户端来连接服务器 Tcp比udp消耗过多资源 Upd类似于半双工&#…...
前端XMLHttpRequest、Fetch API、Axios实现文件上传、下载方法及后端Spring文件服务器处理方法
前言 本文总结Web应用开发中文件上传、下载的方法,即从前端表单输入文件并封装表单数据,然后请求后端服务器的处理过程;从基础的JavaScript中XmlHttpRequest对象、Fetch API实现上传、下载进行说明,并给出了前端常用的axios库的请…...
STM32智能交通监测系统教程
目录 引言环境准备智能交通监测系统基础代码实现:实现智能交通监测系统 4.1 数据采集模块 4.2 数据处理与控制模块 4.3 通信与网络系统实现 4.4 用户界面与数据可视化应用场景:交通监测与管理问题解决方案与优化收尾与总结 1. 引言 智能交通监测系统通…...
【利用Selenium+autoIt实现文件上传】
利用Selenium+autoIt实现文件上传 利用Selenium+autoIT实现文件上传autoIt脚本制作转换成exe文件java代码运行部分利用Selenium+autoIT实现文件上传 当你看到这篇文章时,证明你遇到了和我一样的难题。正常情况下我们利用selenium完全可以实现表单的提交和文件上传等操作。但当…...
python join
1、join函数 *.join(seq) 以*作为分隔符,将seq所有的元素合并为一个新的字符串 seq ABDWDPO new_seq list(.joint(seq)) # ABDWDPO #[A, B, D, W, D, P, O]...
cython加速python代码
python这个语言在使用的层面上看几乎没有缺点,简单易学,语法简单,唯一的弱点就是慢, 当然了万能的python社区是给了解决方法的,那就是cython 使用Cython可以显著提升Python代码的执行效率,特别是在涉及到数…...
React@16.x(60)Redux@4.x(9)- 实现 applyMiddleware
目录 1,applyMiddleware 原理2,实现2.1,applyMiddleware2.1.1,compose 方法2.1.2,applyMiddleware 2.2,修改 createStore 接上篇文章:Redux中间件介绍。 1,applyMiddleware 原理 R…...
level 6 day1 Linux网络编程之网络基础
v1 网络的历史和分层 TCP 是可靠传输,IP协议是不可靠传输 网络的体系结构 网络分层的思想: OSI体系结构 两层交换机是指数据链路层的交换 三层交换是指网络层这边的交换 四层模型 蓝色的字 是由手机发给PC机,由传输层来决定应该交给哪一…...
PostgreSQL UPDATE 命令
PostgreSQL UPDATE 命令 PostgreSQL 是一种功能强大的开源对象关系型数据库管理系统(ORDBMS),它使用并扩展了SQL语言。在处理数据库时,我们经常需要更新现有的记录。在PostgreSQL中,UPDATE命令用于修改表中的现有记录…...
什么? CSS 将支持 if() 函数了?
CSS Working Group 简称 CSSWG, 在近期的会议中决定将 if() 添加到 CSS Values Module Level 5 中。 详情可见:css-meeting-bot 、[css-values] if() function 当我看到这个消息的时候,心中直呼这很逆天了,我们知道像 less 这些 css 这些预…...
function calling实现调用理杏仁api获取数据
LLM是不存在真正逻辑的且并不是知晓万事万物的(至少目前是这样)在很多更垂直的环境下LLM并不能很好的赋能。 function calling的实现使LLM可以对接真正的世界以及真正有逻辑的系统,这将很大程度上改变LLM的可用范围(当然安全问题依…...
Excel中用VBA实现Outlook发送当前工作簿
Excel中用VBA实现Outlook发送当前工作簿,首先按AltF11打开VBA编辑器,插入模块,并在工具-引用中勾选 Microseft Outlook .0 Object Library(其中为你Microseft Outlook的版本号。 Sub 发送邮件() 保存当前excel ThisWorkbook.Save让excel连接…...
从 ArcMap 迁移到 ArcGIS Pro
许多 ArcMap 用户正在因 ArcGIS Pro 所具有的现代 GIS 桌面工作流优势而向其迁移。 ArcGIS Pro 与其余 ArcGIS 平台紧密集成,使您可以更有效地共享和使用内容。 它还将 2D 和 3D 组合到一个应用程序中,使您可以在同一工程中使用多个地图和多个布局。 Arc…...
WSL2 的安装与运行 Linux 系统
前言 适用于 Linux 的 Windows 子系统 (WSL) 是 Windows 的一项功能,允许开发人员在 Windows 系统上直接安装并使用 Linux 发行版。不用进行任何修改,也无需承担传统虚拟机或双启动设置的开销。 可以将 WSL 看作也是一个虚拟机,但是它更为便…...
Linux应用开发之网络套接字编程(实例篇)
服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …...
国防科技大学计算机基础课程笔记02信息编码
1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制,因此这个了16进制的数据既可以翻译成为这个机器码,也可以翻译成为这个国标码,所以这个时候很容易会出现这个歧义的情况; 因此,我们的这个国…...
云计算——弹性云计算器(ECS)
弹性云服务器:ECS 概述 云计算重构了ICT系统,云计算平台厂商推出使得厂家能够主要关注应用管理而非平台管理的云平台,包含如下主要概念。 ECS(Elastic Cloud Server):即弹性云服务器,是云计算…...
《Playwright:微软的自动化测试工具详解》
Playwright 简介:声明内容来自网络,将内容拼接整理出来的文档 Playwright 是微软开发的自动化测试工具,支持 Chrome、Firefox、Safari 等主流浏览器,提供多语言 API(Python、JavaScript、Java、.NET)。它的特点包括&a…...
UDP(Echoserver)
网络命令 Ping 命令 检测网络是否连通 使用方法: ping -c 次数 网址ping -c 3 www.baidu.comnetstat 命令 netstat 是一个用来查看网络状态的重要工具. 语法:netstat [选项] 功能:查看网络状态 常用选项: n 拒绝显示别名&#…...
蓝桥杯 2024 15届国赛 A组 儿童节快乐
P10576 [蓝桥杯 2024 国 A] 儿童节快乐 题目描述 五彩斑斓的气球在蓝天下悠然飘荡,轻快的音乐在耳边持续回荡,小朋友们手牵着手一同畅快欢笑。在这样一片安乐祥和的氛围下,六一来了。 今天是六一儿童节,小蓝老师为了让大家在节…...
el-switch文字内置
el-switch文字内置 效果 vue <div style"color:#ffffff;font-size:14px;float:left;margin-bottom:5px;margin-right:5px;">自动加载</div> <el-switch v-model"value" active-color"#3E99FB" inactive-color"#DCDFE6"…...
leetcodeSQL解题:3564. 季节性销售分析
leetcodeSQL解题:3564. 季节性销售分析 题目: 表:sales ---------------------- | Column Name | Type | ---------------------- | sale_id | int | | product_id | int | | sale_date | date | | quantity | int | | price | decimal | -…...
k8s业务程序联调工具-KtConnect
概述 原理 工具作用是建立了一个从本地到集群的单向VPN,根据VPN原理,打通两个内网必然需要借助一个公共中继节点,ktconnect工具巧妙的利用k8s原生的portforward能力,简化了建立连接的过程,apiserver间接起到了中继节…...
3403. 从盒子中找出字典序最大的字符串 I
3403. 从盒子中找出字典序最大的字符串 I 题目链接:3403. 从盒子中找出字典序最大的字符串 I 代码如下: class Solution { public:string answerString(string word, int numFriends) {if (numFriends 1) {return word;}string res;for (int i 0;i &…...
