设计模式-工厂模式Factory

工厂模式

  • b.工厂方法模式 (Factory Method) (重点)
    • 1) 简单工厂 Simple Factory
      • 1.a) 简单工厂的好处
    • 2) 工厂方法 Factory Method
      • 2.a) 对工厂进行抽象化 (版本一)
      • 2.b) 对工厂进行缓存 (版本二)
      • 2.c) 加载配置文件到缓存中 (版本三)
        • c.1) 产品线
  • c.抽象工厂模式 (Abstract Factory)
  • 总结:3种工厂模式

b.工厂方法模式 (Factory Method) (重点)

一般情况下,工厂模式分为三种更加细分的类型:简单工厂、工厂方法和抽象工厂。

1) 简单工厂 Simple Factory

简单工厂叫作静态工厂方法模式(Static Factory Method Pattern)

现在有一个场景,需要一个资源加载器,要根据不用的url进行资源加载,但是如果我们将所有的加载实现代码全部封装在了一个load方法中,就会导致一个类很大,同时扩展性也非常差,当想要添加新的前缀解析其他类型的url时,发现需要修改大量的源代码。

定义两个需要之后会用到的类

/*** 事实上一个资源类会比这个负责*/
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Resource {private String url;
}
public class ResourceLoader {/*** 资源加载* @param url file:// http:// classpath://* @return 资源类*/public Resource load(String url) {// 1.根据url获取前缀String prefix = getPreFix(url);Resource resource = null;// 2.根据前缀,处理不同的资源(有很多的分支逻辑)if ("http".equals(prefix)){// 发起http请求下载资源....(代码复杂)return new Resource(url);} else if ("file".equals(prefix)) {// 建立流,做异常...(代码复杂)return new Resource(url);} else if ("classpath".equals(prefix)) {// ......(代码复杂)return new Resource(url);}else {return new Resource("default");}}/*** 获取前缀* @param url* @return*/private String getPreFix(String url) {if (url == null || url.equals("") || !url.contains("://")){throw new ResourceLoadException("传入的资源url不合法");}String[] split = url.split(":");return split[0];}
}

在上边的案例中,存在很多的if分支,如果分支数量不多,且不需要扩展,这样的编写方式当然没错,然而在实际的工作场景中,我们的业务代码可能会很多,分支逻辑也可能十分复杂,这个时候简单工厂设计模式就要发挥作用了。

只需要创建一个工厂类,将创建资源的能力交给工厂即可:

/*** 简单工厂*/
public class ResourceFactory {public static Resource create(String type, String url) {if ("http".equals(type)){// 发起http请求下载资源....(代码复杂)return new Resource(url);} else if ("file".equals(type)) {// 建立流,做异常...(代码复杂)return new Resource(url);} else if ("classpath".equals(type)) {// ......(代码复杂)return new Resource(url);}else {return new Resource("default");}}
}

有了工厂之后,我们的主要逻辑就会简化:

public Resource load(String url) {// 1.根据url获取前缀String prefix = getPreFix(url);// 2.根据前缀,处理不同的资源(有很多的分支逻辑)return ResourceLoaderFactory.create(prefix, url);
}

1.a) 简单工厂的好处

这就是简单工厂设计模式,提取一个工厂类,工厂会根据传入的不同的类型,创建不同的产品,将创建对象的过程交给工厂类、其他业务需要某个产品时,直接使用create()创建,好处如下:

  • 1.工厂将创建的过程进行封装,不需要关系创建的细节,更加符合面向对象思想
  • 2.主要的业务逻辑不会被创建对象的代码干扰,代码更易阅读
  • 3.产品的创建可以独立测试,更将容易测试
  • 4.独立的工厂类只负责创建产品,更加符合单一原则

2) 工厂方法 Factory Method

对上面的代码进行重构。我们会为每一个 Resource 创建一个独立的工厂类,形成一个个小作坊,将每一个实例的创建过程交给工厂类完成。

2.a) 对工厂进行抽象化 (版本一)

回到的例子中,每一种url加载成不同的资源产品,那每一种资源都可以由一个独立的ResourceFactory生产,需要将生产资源的工厂类进行抽象:

public interface IResourceLoader {Resource load(String url);
}

为每一种资源创建与之匹配的实现:

// Http
public class HttpResourceLoader implements IResourceLoader {@Overridepublic Resource load(String url) {// 省略复杂处理的创建过程return new Resource(url);}
}// File
public class FileResourceLoader implements IResourceLoader {@Overridepublic Resource load(String url) {// 省略复杂处理的创建过程return new Resource(url);}
}// Classpath
public class ClasspathResourceLoader implements IResourceLoader {@Overridepublic Resource load(String url) {// 省略复杂处理的创建过程return new Resource(url);}
}// Default
public class DefaultResourceLoader implements IResourceLoader {@Overridepublic Resource load(String url) {// 省略复杂处理的创建过程return new Resource(url);}
}

主要逻辑就会简化

private IResourceLoader resourceLoader;public Resource load(String url) {// 1.根据url获取前缀String prefix = getPreFix(url);// 2.根据前缀选择不同的工厂,生产独自的产品if ("http".equals(prefix)){// 发起http请求下载资源....(代码复杂)resourceLoader = new HttpResourceLoader();} else if ("file".equals(prefix)) {// 建立流,做异常...(代码复杂)resourceLoader = new FileResourceLoader();} else if ("classpath".equals(prefix)) {// ......(代码复杂)resourceLoader = new ClasspathResourceLoader();}else {resourceLoader = new DefaultResourceLoader();}return resourceLoader.load(url);
}

当我们新增一种读取资源的方式时,只需要新增一个实现,并实现 IResourceLoader 接口即可。所以,工厂方法模式比起简单工厂模式更加符合开闭原则。

2.b) 对工厂进行缓存 (版本二)

们为每个产品引入了工厂,却发现需要为创建工厂这个行为付出代价,在创建工厂这件事上,仍然不符合开闭原则

为了解决上述的问题我们又不得不去创建一个工厂的缓存来统一管理工厂实例,以后使用工厂会更加的简单

主要业务逻辑优化为:使用静态代码块和Map进行缓存

// 略....
private static Map<String, IResourceLoader> resourceLoaderCache = new HashMap<>();static {resourceLoaderCache.put("http", new HttpResourceLoader());resourceLoaderCache.put("file", new FileResourceLoader());resourceLoaderCache.put("classpath", new ClasspathResourceLoader());resourceLoaderCache.put("default", new DefaultResourceLoader());
}/*** 资源加载* @param url file:// http:// classpath://* @return 资源类*/
public Resource load(String url) {// 1.根据url获取前缀String prefix = getPreFix(url);// 2return resourceLoaderCache.get(prefix).load(url);
}
// 略....

2.c) 加载配置文件到缓存中 (版本三)

但是,当修改需求还是不够灵活,仍然需要修改static中的代码,将工厂类进行配置,加载配置文件到缓存中

创建resourceLoad.properties

http=com.dcy.factoryMethod.resourceFactory.impl.HttpResourceLoader
file=com.dcy.factoryMethod.resourceFactory.impl.FileResourceLoader
classpath=com.dcy.factoryMethod.resourceFactory.impl.ClasspathResourceLoader
default=com.dcy.factoryMethod.resourceFactory.impl.DefaultResourceLoader

主要业务逻辑优化为:

// 略...
private static Map<String, IResourceLoader> resourceLoaderCache = new HashMap<>();static {InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("resourceLoad.properties");Properties properties = new Properties();try {properties.load(inputStream);for (Map.Entry<Object, Object> entry : properties.entrySet()) {String key = entry.getKey().toString();Class<?> clazz = Class.forName(entry.getValue().toString());IResourceLoader loader = (IResourceLoader) clazz.getConstructor().newInstance();resourceLoaderCache.put(key, loader);}} catch (Exception e) {throw new RuntimeException(e);}
}public Resource load(String url) {// 1.根据url获取前缀String prefix = getPreFix(url);// 2.return resourceLoaderCache.get(prefix).load(url);
}// 略...

好处:

  • 1.完成满足开闭原则。当需要扩充需求是,只需要新增实现和修改配置文件,不需要修改一行代码
  • 2.完全解耦

c.1) 产品线

事实上,在工作中,我们的产品可能是及其复杂的,我们同样需要对整个产品线进行抽象

public abstract class AbstractResource {private String url;public AbstractResource() {}public AbstractResource(String url) {this.url = url;}protected void shared() {System.out.println("这是共享方法");}/*** 每个子类需要独自实现的方法* @return 字节流*/public abstract InputStream getInputStream();}

为每一种产品创建与之匹配的实现:

// classpath
public class ClasspathResource extends AbstractResource {public ClasspathResource() {}public ClasspathResource(String url) {super(url);}@Overridepublic InputStream getInputStream() {return null;}
}// Http
public class HttpResource extends AbstractResource {public HttpResource() {}public HttpResource(String url) {super(url);}@Overridepublic InputStream getInputStream() {return null;}
}// Default
public class DefaultResource extends AbstractResource {public DefaultResource() {}public DefaultResource(String url) {super(url);}@Overridepublic InputStream getInputStream() {return null;}
}

修改IResourceLoader接口,将返回一个产品的资源

public interface IResourceLoader {AbstractResource load(String url);
}

修改实现工厂的方法

public class ClasspathResourceLoader implements IResourceLoader {@Overridepublic AbstractResource load(String url) {// 省略复杂处理的创建过程return new ClasspathResource(url);}
}

主要 加载资源load()业务逻辑修改为

public AbstractResource load(String url) {// 1.根据url获取前缀String prefix = getPreFix(url);// 2.return resourceLoaderCache.get(prefix).load(url);
}

在这里插入图片描述

c.抽象工厂模式 (Abstract Factory)

重要性比不上简单工厂和工厂方法

抽象工厂模式是工厂方法模式的升级版本,在有多个业务品种、业务分类时,通过抽象工厂模式生产需要的对象是一种不错的解决方案。

多种类型的产品,即增加产品族。如:抽象多种产品类型

public interface Resource {InputStream getInputStream();
}
// 抽象文本类型
public abstract class AbstractTextResource implements Resource {// 略....
}// 抽象图片类型
public abstract class AbstractPictureResource implements Resource {// 略....
}// 抽象视频资源
public abstract class AbstractVideoResource implements Resource {// 略....
}

每一个工厂实例都可以生产一个产品族

/*** IResourceFactory,每一个工厂实例都可以生产一个产品族*/
public interface IResourceLoader {/*** 加载图片资源的工厂方法* @param url 资源路径* @return 图片资源*/AbstractPictureResource loadPicture(String url);/*** 加载视频资源的工厂方法* @param url 资源路径* @return 视频资源*/AbstractVideoResource loadVideo(String url);/*** 加载文本资源的工厂方法* @param url 资源路径* @return 文本资源*/AbstractTextResource loadText(String url);
}

每一个工厂都对应各自的产品生产线

// Classpath
public class ClasspathResourceLoader implements IResourceLoader {@Overridepublic AbstractPictureResource loadPicture(String url) {return new ClasspathPictureResource(url);}@Overridepublic AbstractVideoResource loadVideo(String url) {return new ClasspathVideoResource(url);}@Overridepublic AbstractTextResource loadText(String url) {return new ClasspathTextResource(url);}
}
// Default
public class DefaultResourceLoader implements IResourceLoader {@Overridepublic AbstractPictureResource loadPicture(String url) {return new DefaultPictureResource(url);}@Overridepublic AbstractVideoResource loadVideo(String url) {return new DefaultVideoResource(url);}@Overridepublic AbstractTextResource loadText(String url) {return new DefaultTextResource(url);}
}
// Http
public class HttpResourceLoader implements IResourceLoader {@Overridepublic AbstractPictureResource loadPicture(String url) {return new HttpPictureResource(url);}@Overridepublic AbstractVideoResource loadVideo(String url) {return new HttpVideoResource(url);}@Overridepublic AbstractTextResource loadText(String url) {return new HttpTextResource(url);}
}

在这里插入图片描述

总结:3种工厂模式

简单工厂:一个工厂生产不同产品

静态资源工厂(工厂方法):一个工厂生产一个产品

抽象工厂:一个工厂生产一个产品族,每一个产品族之间可能会有共享或交叉

当创建逻辑比较复杂,是一个“大工程”的时候,我们就应该考虑使用工厂模式,封装对象的创建过程,将对象的创建和使用相分离

何为创建逻辑比较复杂呢?

  • 类似规则配置解析的例子,代码中存在 if-else 分支判断。将这一大坨 if-else 创建对象的代码抽离出来,放到工厂类中。
  • 单个对象本身的**创建过程比较复杂,**比如前面提到的要组合其他类对象,做各种初始化操作。在这种情况下,我们也可以考虑使用工厂模式,将对象的创建过程封装到工厂类中。

工厂模式的作用:

  • 1.封装变化:创建逻辑有可能变化,封装成工厂类之后,创建逻辑的变更对调用者透明。
  • 2.代码复用:创建代码抽离到独立的工厂类之后可以复用。
  • 3.隔离复杂性:封装复杂的创建逻辑,调用者无需了解如何创建对象。
  • 4.控制复杂度:将创建代码抽离出来,让原本的函数或类职责更单一,代码更简洁,测试更方便

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/68169.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SQL-DQL

-----分组查询----- 1.语法&#xff1a; SELECT 字段列表 FROM 表名 [WHERE 条件 ] GROUP BY 分组字段名 [HAVING 分组后过滤条件]&#xff1b; 2.where与having区别 》执行时机不同&#xff1a;where是分组之前进行过滤&#xff0c;不满足where条件&#xff0c;不参与分组&…

【Flutter】Flutter 使用 qr_flutter 实现QR码二维码生成与渲染

【Flutter】Flutter 使用 qr_flutter 实现QR码二维码生成与渲染 文章目录 一、前言二、qr_flutter 包简介三、安装与配置四、基本使用五、高级功能与自定义六、完整实际业务代码示例七、总结 一、前言 亲爱的 Flutter 初学者&#xff0c;你好&#xff01;今天我要与你分享一个…

【黑马头条之项目部署_持续集成Jenkins】

本笔记内容为黑马头条项目的项目部署_持续集成部分 目录 一、内容介绍 1、什么是持续集成 2、持续集成的好处 3、今日内容 二、软件开发模式 1、软件开发生命周期 2、软件开发瀑布模型 3、软件的敏捷开发 三、Jenkins安装配置 1、Jenkins介绍 2、Jenkins环境搭建 …

OJ题库:计算日期到天数转换、打印从1到最大的n位数 、尼科彻斯定理

前言&#xff1a;在部分大厂笔试时经常会使用OJ题目&#xff0c;这里对《华为机试》和《剑指offer》中的部分题目进行思路分析和讲解&#xff0c;希望对各位读者有所帮助。 题目来自牛客网&#xff0c;欢迎各位积极挑战&#xff1a; HJ73:计算日期到天数转换_牛客网 JZ17:打印…

【【STM32-29正点原子版本串口发送传输实验】

STM32-29正点原子版本串口发送传输实验 通过串口接收或发送一个字符 例程目的 开发板上我们接入的是实现异步通信的UART接口 USB转串口原理图 我们一步步分析 PA9是串口1 的发送引脚 PA10是串口1 的接受引脚 。因为我们现在只是用到异步收发器功能&#xff0c;所以我们现…

qt中子窗口最小化后再恢复显示窗口区域显示为全白色

问题&#xff1a; qt中子窗口最小化后再恢复显示窗口区域显示为全白色&#xff0c;如下图&#xff1a; 原因&#xff1a; 恢复显示后窗口为及时刷新。 解决办法&#xff1a; 重写showEvent函数&#xff0c;如下&#xff1a; void MyClass::showEvent(QShowEvent *event) {se…

大模型 Dalle2 学习三部曲(二)clip学习

clip论文比较长48页&#xff0c;但是clip模型本身又比较简单&#xff0c;效果又奇好&#xff0c;正所谓大道至简&#xff0c;我们来学习一下clip论文中的一些技巧&#xff0c;可以让我们快速加深对clip模型的理解&#xff0c;以及大模型对推荐带来革命性的变化。 clip结构 首选…

Mybatis 动态SQL – 使用choose标签动态生成条件语句

之前我们介绍了if,where标签的使用&#xff1b;本篇我们需要在if,where标签的基础上介绍如何使用Mybatis提供的choose标签动态生成条件语句。 如果您对if,where标签动态生成条件语句不太了解&#xff0c;建议您先进行了解后再阅读本篇&#xff0c;可以参考&#xff1a; Mybat…

CSS中如何实现文字描边效果(Text Stroke)?

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 文字描边效果&#xff08;Text Stroke&#xff09;⭐ 示例⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门之旅&#xff01;这个…

Go实现LogCollect:海量日志收集系统【上篇——LogAgent实现】

Go实现LogCollect&#xff1a;海量日志收集系统【上篇——LogAgent实现】 下篇&#xff1a;Go实现LogCollect&#xff1a;海量日志收集系统【下篇——开发LogTransfer】 项目架构图&#xff1a; 0 项目背景与方案选择 背景 当公司发展的越来越大&#xff0c;业务越来越复杂…

Mysql底层数据结构为什么选择B+树

索引底层采用什么数据结构&#xff0c;为什么使用B树而不是其他数据结构&#xff1a; &#xff08;1&#xff09;如果采用二叉树&#xff1a;使用递增字段作为索引时&#xff0c;二叉树会退化成链表&#xff0c;查找效率太低 &#xff08;2&#xff09;如果采用红黑树&#xf…

微信小程序开发:一种新型的移动应用程序开发方式

一、引言 随着移动互联网的快速发展&#xff0c;微信小程序作为一种新型的移动应用开发方式&#xff0c;正在受到越来越多的关注。微信小程序是一种基于微信平台的轻量化应用&#xff0c;开发者可以通过微信提供的开发工具和接口&#xff0c;开发出各种具有特定功能的应用程序…

如何创建一个自己的sphinx文档网站

文章目录 前言一、操作步骤1.安装anaconda2.启动python3.8环境3.安装Sphinx4.创建文件夹5.初始化环境6. 编译7.文件夹搭查看8.搭建nginx查看8. 更换主题9.错误修复10.这里提供两个模板1.Demo_md2.Demo_rst前言 最近看到公司的文档中心,突然想起,为什么不为自己创建一个文档中…

在k8s中用label控制Pod部署到指定的node上

案例-标注k8s-node1是配置了SSD的节点 kubectl label node k8s-node1 disktypessd 查看标记 测试 将pod部署到disktypessd的节点上&#xff08;这里设置了k8s-node1为ssd&#xff09; 部署后查看结果-副本全都运行在了k8s-node1上—符合预期 删除标记 kubectl label node k8…

顶尖211“小清华”!强过985,不要错过它!

一、学校及专业介绍 西安电子科技大学&#xff08;Xidian University&#xff09;&#xff0c;简称“西电” &#xff0c;位于陕西省西安市&#xff0c;是中央部属高校&#xff0c;直属于教育部&#xff0c;为全国重点大学&#xff0c;位列国家“双一流”“211工程”&#xff…

MySQL的内置函数复合查询内外连接

文章目录 内置函数时间函数字符串函数数学函数其他函数 复合查询多表笛卡尔积自连接在where中使用子查询多列子查询在from中使用子查询 内连接外连接左外连接右外连接 内置函数 时间函数 函数描述current_date()当前日期current_time()当前时间current_timestamp()当前时间戳…

Vagrant命令

文章目录 1.介绍2.下载3. 配置3.1 配置环境变量3.2 在xshell中连接使用 4. 相关命令4.1 Box相关4.2 初始化环境4.4 虚拟机相关 1.介绍 Vagrant 是一个虚拟机管理工具 2.下载 https://www.vagrantup.com/ 3. 配置 3.1 配置环境变量 测试安装是否成功 3.2 在xshell中连接使…

码云使用记录

码云使用记录 主要步骤 1、https://gitee.com 注册 2、下载Git 3、配置SSH 4、创建远程仓库 5、切到本地项目目录下将本地项目推到远程 前两步根据提示进行即可&#xff0c;下面从第三步开始讲解 3、配置SSH&#xff08;用于提交代码和更新代码&#xff09; https://gitee.…

机器人中的数值优化(七)——修正阻尼牛顿法

本系列文章主要是我在学习《数值优化》过程中的一些笔记和相关思考&#xff0c;主要的学习资料是深蓝学院的课程《机器人中的数值优化》和高立编著的《数值最优化方法》等&#xff0c;本系列文章篇数较多&#xff0c;不定期更新&#xff0c;上半部分介绍无约束优化&#xff0c;…

CCKS2023:基于企业数仓和大语言模型构建面向场景的智能应用

8月24日-27日&#xff0c;第十七届全国知识图谱与语义计算大会&#xff08;CCKS 2023&#xff09;在沈阳召开。大会以“知识图谱赋能通用AI”为主题&#xff0c;探讨知识图谱对通用AI技术的支撑能力&#xff0c;探索知识图谱在跨平台、跨领域等AI任务中的作用和应用途径。 作为…