上帝视角俯视工厂设计模式

引言

本篇聊聊设计模式中的简单工厂、工厂方法、抽象工厂设计模式,争取在看完这篇后不会再傻傻分不清以及能够应用在实际项目中

背景

以一个咱们都熟悉的场景举个例子,我们平时都会戴口罩,用来过滤一些普通病毒,大致的设计如下图,每当有一个新的用户需要用到口罩时,都自己new一个普通口罩
在这里插入图片描述

那么现在新冠来了,经过研究发现N95口罩可以更好的抵御新冠,那么要怎么改呢,大致设计如下图
在这里插入图片描述

就是对每一个使用普通口罩的用户改为N95口罩。假设这里的用户很多有上百个,那对应到代码就是我们要去上百个地方进行代码改动,这个改动成本以及风险都是比较大的,如果后续又有效果更好的口罩呢,是不是这么一想就觉得头都大了,这些问题也是切切实实的发生在我们的工作中。以下是以Java实现的案例

对应的普通口罩实现

public class Main {public static void main(String[] args) {CommonFaceMask commonFaceMask1 = new CommonFaceMask();commonFaceMask1.filterViruses();CommonFaceMask commonFaceMask2 = new CommonFaceMask();commonFaceMask2.filterViruses();CommonFaceMask commonFaceMask3 = new CommonFaceMask();commonFaceMask3.filterViruses();}
}class CommonFaceMask {public CommonFaceMask() {System.out.println("创建了一个新的口罩");}public void filterViruses() {System.out.println("过滤普通的病毒");}
}

对应的N95口罩实现

public class Main {public static void main(String[] args) {//CommonFaceMask commonFaceMask1 = new CommonFaceMask();N95FaceMask commonFaceMask1 = new N95FaceMask();commonFaceMask1.filterViruses();//CommonFaceMask commonFaceMask2 = new CommonFaceMask();N95FaceMask commonFaceMask2 = new N95FaceMask();commonFaceMask2.filterViruses();//CommonFaceMask commonFaceMask3 = new CommonFaceMask();N95FaceMask commonFaceMask3 = new N95FaceMask();commonFaceMask3.filterViruses();}
}class N95FaceMask {public N95FaceMask() {System.out.println("创建了一个新的N95口罩");}public void filterViruses() {System.out.println("过滤新冠病毒");}
}

通过上述代码能够清晰的感知到,所有类似的变动可能会导致大幅的代码适配工作;但是别担心,让我们来看看三个不同的工厂模式分别是怎么解决这个问题的

简单工厂

通过思考我们能够发现,之所以会有这么大的改动是因为用户跟口罩之间是强依赖关系,口罩的变动会影响到每一个用户,解决强依赖的方式是什么?本质上都是通过在中间层加入一个代理也就是今天咱们要讨论的“工厂”,引入后的图如下
在这里插入图片描述

由上图能够清晰的看到用户不再依赖具体的口罩类型,只需要由工厂统一返回即可,工厂返回什么类型的口罩用户不关心,用户只需要按照口罩的“使用方式”来使用即可,这样如果口罩类型有变动,只需要通知这家工厂即可,无需影响到用户(本身也不需要)。

因此我们可知,在设计程序时我们不该面向实现细节,应该面向抽象,例如用口罩的时候其实我们关心的不是口罩是怎么制作、什么牌子的,只关心它能不能过滤病毒、细菌对吧?因此我们提炼抽象出一个口罩接口(也就是口罩的“使用方式”),这个接口有一个filterViruses方法就是过滤病毒,所有口罩都要实现这个接口并实现改方法

public interface FaceMask {void filterViruses();
}class N95FaceMask implements FaceMask {public N95FaceMask() {System.out.println("创建了一个新的N95口罩");}public void filterViruses() {System.out.println("过滤新冠病毒");}
}class CommonFaceMask implements FaceMask {public CommonFaceMask() {System.out.println("创建了一个新的普通口罩");}public void filterViruses() {System.out.println("过滤普通的普通病毒");}
}

接下来看看工厂类的实现

public class FaceMaskFactory {//在某些场景就是要跟口罩的型号绑定,那么也可以通过指定口罩的名称来进行获取public FaceMask createFaceMask(String faceMaskName) {switch (faceMaskName) {case "Common":return new CommonFaceMask();case "N95":return new N95FaceMask();default:return new CommonFaceMask();}}//这里根据读取配置动态决定,这里为了演示先硬编码为 N95public FaceMask createFaceMask() {String faceMaskName = "N95";switch (faceMaskName) {case "Common":return new CommonFaceMask();case "N95":return new N95FaceMask();default:return new CommonFaceMask();}}
}

再来看看具体使用方式

public class Main {public static void main(String[] args) {FaceMaskFactory faceMaskFactory = new FaceMaskFactory();FaceMask faceMask1 = faceMaskFactory.createFaceMask();faceMask1.filterViruses();FaceMask faceMask2 = faceMaskFactory.createFaceMask();faceMask2.filterViruses();FaceMask faceMask3 = faceMaskFactory.createFaceMask();faceMask3.filterViruses();}
}

以上就是简单工厂模式的实现,它实现了对象创建和使用的职责分离,或者说将对象创建的权力收敛到工厂类里面了。客户端不知道也不需要知道所获取的对象是怎么创建的以及怎么初始化的,只需要按照对象的接口规范进行使用即可,这个场景非常广泛,例如我们无需关心灯泡是什么品牌多少瓦,只要能按照规范拧进灯座就能亮,不需要知道汽车的发动机是怎么制造汽车是怎么设计只需要按照规范踩动油门汽车就能行驶等等

通过结合配置文件的方式,即便要替换新的口罩类型,可以在不修改客户端代码的情况下实现同时也在一定程度上提高了系统的灵活性,这也是工作中会经常用到的。除此之外通过工厂模式可以很好的进行监控以及管理对象。缺点是在产品类型比较多或者有些产品的创建/初始化逻辑比较多,可能会造成工厂逻辑过于复杂,不利于系统的拓展维护,为了解决这个问题出了工厂方法模式

痛点:简单工厂模式虽然实现简单理解简单且很好解耦了客户端跟对象的关系,但它将过多的逻辑集中在一个工厂类里,当这些逻辑扩张时,这个工厂类会变得很臃肿且难以维护,另一方面,在涉及到新建对象逻辑变动时会频繁的改动这里的代码,一旦出了问题影响的范围会很广泛

工厂方法

接下来看看工厂方法的实现,既然简单工厂方法的瓶颈在工厂类,那么对它进行抽象下,定义一个工厂接口,只负责工厂的定义,具体工厂的实现逻辑延迟到子类。这样本质上将一家工厂改为按业务划分为多家工厂,通过工厂接口定义了工厂生产什么,工厂实现类再做具体的实现逻辑
在这里插入图片描述

通过这种方式避免了过多逻辑集中在一个工厂类中,而是针对产品类型纬度进行划分,不同的产品类型有自己的独立工厂,这样即便后续有新的产品也不会影响到普通工厂和N95工厂,只需增加新的工厂实现类即可

代码实现如下

public interface FaceMask {void filterViruses();
}class N95FaceMask implements FaceMask {public N95FaceMask() {System.out.println("创建了一个新的N95口罩");}public void filterViruses() {System.out.println("过滤新冠病毒");}
}class CommonFaceMask implements FaceMask {public CommonFaceMask() {System.out.println("创建了一个新的普通口罩");}public void filterViruses() {System.out.println("过滤普通的普通病毒");}
}

工厂类的实现

public interface IFactory {FaceMask createFaceMask();
}class N95Factory implements IFactory {@Overridepublic FaceMask createFaceMask() {return new N95FaceMask();}
}class CommonFactory implements IFactory {@Overridepublic FaceMask createFaceMask() {return new CommonFaceMask();}
}

使用方式

public class Main {public static void main(String[] args) {IFactory iFactory1 = new N95Factory();FaceMask faceMask1 = iFactory1.createFaceMask();faceMask1.filterViruses();FaceMask faceMask2 = iFactory1.createFaceMask();faceMask2.filterViruses();FaceMask faceMask3 = iFactory1.createFaceMask();faceMask3.filterViruses();IFactory iFactory2 = new CommonFactory();FaceMask faceMask4 = iFactory2.createFaceMask();faceMask4.filterViruses();FaceMask faceMask5 = iFactory2.createFaceMask();faceMask5.filterViruses();FaceMask faceMask6 = iFactory2.createFaceMask();faceMask6.filterViruses();}
}

通过这里能够清晰的看到,已经创建好的工厂逻辑不会再进行变动,如果有新的产品,只需要根据工厂接口定义的规范实现对应产品的工厂类即可。这样设计符合开闭原则,面向修改关闭,面向拓展开放。将原来集中在一个工厂类的臃肿逻辑拆分到多个“各司其职”的工厂实现类中,提升了可读性以及可维护性,并大幅降低了由于新的改动影响之前实现逻辑的情况

痛点:虽然工厂方法模式解决了简单工厂模式的臃肿以及相互影响的问题,但是每次有新的产品时不仅要创建对应的产品类,还要创建对应的产品工厂类,也会导致过多的产品工厂类,增加系统的复杂度

以上就是工厂方法设计模式,我们想想,如果现在不仅要生产口罩,还要生产其他的例如消毒酒精的话要如何实现呢,如果还是基于工厂方法设计模式的话可能要单独给酒精创建工厂并针对不同的酒精创建对应的工厂实现类,那有同学要问了,能否复用下工厂呢?是可以的这也是接下来要聊的抽象工厂模式

抽象工厂模式

通过上述我们可以发现每一个产品都会有对应的产品对象类以及对应的工厂类,久而久之可能会诞生过多的工厂类增加系统的复杂度。那能不能做下“合并”呢,可以从业务场景出发进行划分,例如这里就按规格进行划分,高规格的工厂负责生产N95口罩和75度的酒精,低规格的工厂负责生产普通口罩和60度的酒精。这样划分是不是跟我们生活中的工厂更相似、效率更高些?
在这里插入图片描述

代码实现如下,首先是创建口罩相关的接口以及实现类

public interface FaceMask {void filterViruses();
}class N95FaceMask implements FaceMask {public N95FaceMask() {System.out.println("创建了一个新的N95口罩");}public void filterViruses() {System.out.println("过滤新冠病毒");}
}class CommonFaceMask implements FaceMask {public CommonFaceMask() {System.out.println("创建了一个新的普通口罩");}public void filterViruses() {System.out.println("过滤普通的普通病毒");}
}

接着是创建酒精相关的接口以及实现类

public interface Alcohol {void disinfect();
}class Alcohol60 implements Alcohol {public Alcohol60() {System.out.println("创建了一个60度的酒精");}@Overridepublic void disinfect() {System.out.println("开始消毒,能杀死普通的病毒");}
}class Alcohol75 implements Alcohol {public Alcohol75() {System.out.println("创建了一个75度的酒精");}@Overridepublic void disinfect() {System.out.println("开始消毒,能杀死新冠的病毒");}
}

再接下来是创建工厂以及工厂实现类

public interface IFactory {FaceMask createFaceMask();Alcohol createAlcohol();
}class LowLevelFactory implements IFactory {@Overridepublic FaceMask createFaceMask() {return new CommonFaceMask();}@Overridepublic Alcohol createAlcohol() {return new Alcohol60();}
}class HighLevelFactory implements IFactory {@Overridepublic FaceMask createFaceMask() {return new N95FaceMask();}@Overridepublic Alcohol createAlcohol() {return new Alcohol75();}
}

最后看看使用场景

public class Main {public static void main(String[] args) {IFactory iFactory1 = new LowLevelFactory();FaceMask faceMask1 = iFactory1.createFaceMask();Alcohol alcohol1 = iFactory1.createAlcohol();faceMask1.filterViruses();alcohol1.disinfect();System.out.println("=================");IFactory iFactory2 = new HighLevelFactory();FaceMask faceMask2 = iFactory2.createFaceMask();Alcohol alcohol2 = iFactory2.createAlcohol();faceMask2.filterViruses();alcohol2.disinfect();}
}
总结

是不是回过神来好奇为啥原先的new方式变成了这么复杂的方式,让咱们捋一捋,首先咱们内心要切记一件事,就是每一个设计都是为了解决一个问题,把问题捋清楚了就不怕了。大致的梳理如下
在这里插入图片描述

大致的演变方式如上图,无需死记硬背,理解就好

相同点

三个工厂模式的共同点就是解耦客户端和对象的创建关系,通过引入工厂这么一个中介,产品的创建/初始化逻辑不必再强依赖客户端并这些逻辑分散在项目的各个地方,所有产品创建的变动都不会导致客户端的改动,工厂已经将这些逻辑统一管理起来了,因此基本上所有变动例如增加新的产品、产品实现变更等只需要跟工厂打交道即可。客户端只需要按照这个产品的规范来用即可,这样客户端就能专注于自己的业务逻辑,对象创建相关的就想给工厂统一管理。这个设计不就像极了生活中的用户、工厂、产品之间的角色吗

场景

平时开发代码中像一些存数据的对象以及逻辑简单且不容易变动的场景,使用new即可

创建的对象逻辑比较复杂且可能会涉及到变动的时候,例如数据持久化逻辑的对象,可能是写本地磁盘、数据库或者通过网络存储这种,最好用用简单工厂类进行统一管理

当场景涉及比较复杂时,例如数据有可能存myql、也有可能存oracle或其他,同时还有很多复杂的操作,此时就该进行划分,针对不同的存储引擎设计对应的工厂类,方便后期的扩展以及维护

抽象工厂设计模式相对少见,具体参考上面的实例结合实际的场景权衡使用的利弊

以上就是工厂设计模式的全部,建议读者全部都自己实现一遍找找“感觉”和差异,如果有追求的朋友可以读读这几种工厂实现的源码,这样会对它们有更深刻的理解,码字画图不易,觉得写的不错的朋友别忘了点个赞,感谢

参考资料
  1. https://xie.infoq.cn/article/88c926822394aa1c80847dd2a

  2. https://refactoringguru.cn/design-patterns/abstract-factory

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

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

相关文章

C++矩阵例题分析(3):螺旋矩阵

一、审题 时间限制:1000ms 内存限制:256MB 各平台平均AC率:14.89% 题目描述 输出一个n*n大小的螺旋矩阵。 螺旋矩阵的样子: 输入描述 共一行,一个正整数n,表示矩阵变长的长度…

NGUI基础-Widget

目录 Widget是什么 Widget组件包含的属性 Pivot Depth Size snap Aspect Free Based on Width Based on Height Widget是什么 在Unity UI系统中,"Widget"是指UI元素的基类,它为UI元素提供了位置、大小和锚点等基本属性。通过使用&qu…

LeetCode 2:两数相加

一、题目描述 给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。 请你将两个数相加,并以相同形式返回一个表示和的链表。 你可以假设除了数字 0 之外,这两个…

C-浮点数类型表示

文章目录 尽管最近有些小小的迷茫,但是刷题不能马虎啊!最近在做790. 数的三次方根1,所以回顾一下C语言中是如何表示浮点数类型的。 以下是ChatGPT的回复: Elaborate on floating point types in C, such as tails and exponents…

QT上位机开发(网络程序界面开发)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing 163.com】 传统的上位机对接方式还是以232、485、can为主,随着网络的发展,越来越多的设备都是以网络进行通信的。毕竟相比较之前&…

java练习题之List(ArrayList)集合练习

List集合 习题: 1:完成以下填空: List 接口的特点是元素 有序 (有|无)顺序, 可重复 (可以|不可以)重复; 2:(List)关于List 集合描述正确的是(…

python的getattr和getattribute调用

1 python的getattr和getattribute调用 python类的属性包括类属性、实例属性, 类属性:直接在类顶层赋值,或通过类名.属性名值赋值; 实例属性:通过self赋值,或通过实例名.属性名值赋值; 类实例…

肠道炎症与年龄和阿尔茨海默病病理学相关:一项人类队列研究

谷禾健康 ​阿尔茨海默 研究表明,慢性低水平的炎症(“炎症衰老”)可能是年龄相关疾病的一个介导因素,而肠道微生物通过破坏肠道屏障可能会促进炎症。 虽然老化和阿尔茨海默病(AD)与肠道微生物群组成的改变有…

Mybatis缓存实现方式

文章目录 装饰器模式Cache 接口及核心实现Cache 接口装饰器1. BlockingCache2. FifoCache3. LruCache4. SoftCache5. WeakCache 小结 缓存是优化数据库性能的常用手段之一,我们在实践中经常使用的是 Memcached、Redis 等外部缓存组件,很多持久化框架提供…

SQLAlchemy快速入门

安装依赖 pip install sqlalchemy pip install pymysql创建数据库和表 # 创建数据库 drop database if exists sqlalchemy_demo; create database sqlalchemy_demo character set utf8mb4; use sqlalchemy_demo;# 创建表 drop table if exists user; create table user (id …

ClickHouse基础介绍

目录 前言 1、什么是clickhouse 2、OLAP场景的关键特征 3、列式存储更适合于OLAP场景的原因 4、clickhouse的独特功能 5、clickhouse的缺点 6、性能 6.1、单个大查询的吞吐量 6.2、处理短查询的延迟时间 6.3、处理大量短查询的吞吐量 6.4、数据的写入性能 前言 11月…

Keras实现seq2seq

概述 Seq2Seq是一种深度学习模型,主要用于处理序列到序列的转换问题,如机器翻译、对话生成等。该模型主要由两个循环神经网络(RNN)组成,一个是编码器(Encoder),另一个是解码器…

C++——list容器放入赋值,交换与大小操作

1. list 赋值和交换 功能描述: 给list容器进行赋值,以及交换list容器 函数原型: 1.assign(beg, end); //将[beg, end)区间中的数据拷贝赋值给本身 2.assign(n, elem); //将n个elem拷贝赋值给本身 3.list& operator(const list &lst);//重载等…

Dart调用JS对10000条定位数据滤波

使用Dart调用JS,还是为了练习跨语言调用; 一、编写对应的JS代码 平时在开发时不推荐将算法放在JS里,我这里是简单的做一下数据过滤; 首先生成一些随机定位数据,在实际开发中可以使用真实数据; // 随机定…

git 常用命令 提交commit

提交一个commit git status test.txt 查看 指定文件test.txt 的状态 git add test.txt 添加 指定文件test.txt 到暂存区 git commit test.txt -m "commit msg" 提交 指定文件test.txtgit status …

汽车信息安全--芯片厂、OEM安全启动汇总(1)

目录 1.芯驰E3安全启动 2.STM32 X-CUBE-SBSFU 3.小米澎湃OS安全启动 4.小结 我在前篇文章里详细记录了车规MCU信息安全设计过程关于网络安全架构的思考过程,从芯片原厂、供应商、OEM等角度思考如何建立起完备的信任链; 不过这思考过程仅仅只是一家之言,因此我又对比了国…

vmware虚拟机安装esxi7.0步骤

一、安装准备 1、下载镜像文件 下载链接:https://pan.baidu.com/s/12XmWBCI1zgbpN4lewqYw6g 提取码:mdtx 2、vmware新建一个虚拟机 2.1 选择自定义 2.2 选择ESXi对应版本 2.3 选择稍后安装操作系统 2.4 默认选择 2.5 自定义虚拟机名称及存储位置 2…

forEach方法跳出循环

在for循环中,跳出循环有两种模式: break、continue;但是在forEach中,使用break或者continue都会报错;使用return在forEach不起作用,循环会继续执行,貌似充当了continue的角色。 forEach方法的机…

MyBatis学习二:Mapper代理开发、配置文件完成增删改查、注解开发

前言 公司要求没办法,前端也要了解一下后端知识,这里记录一下自己的学习 学习教程:黑马mybatis教程全套视频教程,2天Mybatis框架从入门到精通 文档: https://mybatis.net.cn/index.html Mapper代理开发 目的 解决…

Jenkins分布式实现: 构建弹性和可扩展的CI/CD环境!

Jenkins是一个流行的开源持续集成(Continuous Integration,CI)和持续交付(Continuous Delivery,CD)工具,它通过自动化构建、测试和部署过程,帮助开发团队更高效地交付软件。Jenkins的…