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

引言

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

背景

以一个咱们都熟悉的场景举个例子,我们平时都会戴口罩,用来过滤一些普通病毒,大致的设计如下图,每当有一个新的用户需要用到口罩时,都自己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();
        }
    }

  	//这里根据读取配置动态决定,这里为了演示先硬编码为 N95
    public 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 {
    @Override
    public FaceMask createFaceMask() {
        return new N95FaceMask();
    }
}

class CommonFactory implements IFactory {

    @Override
    public 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度的酒精");
    }

    @Override
    public void disinfect() {
        System.out.println("开始消毒,能杀死普通的病毒");
    }
}


class Alcohol75 implements Alcohol {

    public Alcohol75() {
        System.out.println("创建了一个75度的酒精");
    }

    @Override
    public void disinfect() {
        System.out.println("开始消毒,能杀死新冠的病毒");
    }
}

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

public interface IFactory {
    FaceMask createFaceMask();
    Alcohol createAlcohol();
}

class LowLevelFactory implements IFactory {

    @Override
    public FaceMask createFaceMask() {
        return new CommonFaceMask();
    }

    @Override
    public Alcohol createAlcohol() {
        return new Alcohol60();
    }
}

class HighLevelFactory implements IFactory {

    @Override
    public FaceMask createFaceMask() {
        return new N95FaceMask();
    }

    @Override
    public 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.mfbz.cn/a/293713.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

电脑记事本怎么打开?电脑记事本打开方法

在日常工作中,许多上班族都习惯于使用电脑记事本记录重要事项、灵感想法或临时任务。电脑记事本轻便、简洁,能够为我们提供便捷的记事体验。那么电脑记事本怎么打开呢?电脑记事本打开方法是什么呢?在Windows电脑上,我们…

手把手教你用Python打造一个语音合成系统

目录 引言 一、了解语音合成技术 1.1 什么是语音合成技术 1.2 语音合成技术的分类 二、准备所需工具和库 2.1 Python编程语言 2.2 TensorFlow深度学习框架 2.3 WaveNet模型 三、搭建语音合成系统 3.1 数据准备 3.2 数据预处理 3.3 构建WaveNet模型 3.4 训练WaveNe…

京东年度数据报告-2023全年度净水器十大热门品牌销量榜单

近年来,随着科技的不断发展和应用,净水器的技术得到持续创新和提高,产品品质和使用效果不断优化,这也进一步提升了净水器的市场竞争力,2023年,净水器市场的销售成绩呈现增长。 根据鲸参谋平台的数据显示&a…

大语言模型占显存的计算和优化

可以优化的地方: per_device_train_batch_size(相当于batch size,越小显存占的越小) gradient_accumulation_steps(per_device_train_batch_size*gradient_accumulation_steps计算梯度的数据数) gradien…

【CSS】设置0.5px的边框宽度

直接写border: 0.5px solid red; 这样在移动端可能会出现问题&#xff0c;下面说下解决办法&#xff1a; 直接上代码&#xff1a; <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-C…

1.4 day4 IO进程线程

使用两个子进程进行文件拷贝&#xff0c;父进程进行资源回收 #include <myhead.h> int main(int argc, const char *argv[]) {//创建一个文件描述符并以只读的方式打开int fd-1;if((fdopen("./test.bmp",O_RDONLY))-1){perror("open error");return…

2023年度全球重大关基安全事件 TOP 10 | FreeBuf 年度盘点

2023年&#xff0c;针对关键信息基础设施的网络攻击已经演变成为了一个全球性的问题&#xff0c;无论是中、美、俄等国际大国&#xff0c;还是诸多小国/地区&#xff0c;无论是经济发达还是落后&#xff0c;都无法保证绝对免疫关键基础设施的攻击。为了保障国家安全和社会稳定&…

Python Selenium如何下载网页中的图片到本地?(Base64编码的图片下载)

前言&#xff1a; 在网页上&#xff0c;图片有时会以Base64编码的形式嵌入在HTML中&#xff0c;而不是作为单独的文件提供。这种方式的优点是可以减少HTTP请求的数量&#xff0c;因为图片数据直接包含在HTML中&#xff0c;不需要额外的请求来获取图片文件。这对于小图片…

java大数据hadoop2.92安装伪分布式文件系统

Apache Hadoop 3.3.6 – Hadoop: Setting up a Single Node Cluster. 1、解压缩到某个路径 /usr/local/hadoop 2、修改配置文件 /usr/local/hadoop/etc/hadoop/hadoop-env.sh export JAVA_HOME/usr/local/javajdk 3、修改配置文件 /usr/local/hadoop/etc/hadoop/core-sit…

Linux-进程间通信_管道

项目场景&#xff1a; 须熟知文件管理和进程方面的基础知识 通过Xshell和VScode 相互进行远程开发&#xff0c;学习进程间通信的其中一种方式——管道。 问题描述 依照我们曾经所学的知识&#xff0c;我们仅仅只能在单个进程中进行数据的交互&#xff0c;但是在实际应用中&a…

geemap学习笔记041:Landsat Collection2系列数据去云算法总结

前言 去云算法是进行数据处理中所要进行一步重要操作&#xff0c;Sentinal-2数据中已经提供了去云算法&#xff0c;但是Landsat Collection2系列数据中并没有提供去云算法&#xff0c;下面就以Landsat 8 Collection2为例进行介绍。 1 导入库并显示地图 import ee import gee…

二进制安装包安装Prometheus插件安装(mysql_exporter)

简介 mysql_exporter是用来收集MysQL或者Mariadb数据库相关指标的&#xff0c;mysql_exporter需要连接到数据库并有相关权限。既可以用二进制安装部署&#xff0c;也可以通过容器形式部署&#xff0c;但为了数据收集的准确性&#xff0c;推荐二进制安装。 一&#xff0c;下载安…

【CSS】浅学一下filter

目录 1、基本概念 2、用法 3、应用案例 更加智能的阴影效果&#xff1a; 元素、网页置灰 元素强调、高亮 毛玻璃效果 调整网页sepia 褐色值可以实现护眼效果 1、基本概念 CSS filter 属性将模糊或颜色偏移等图形效果&#xff08;对比度、亮度、饱和度、模糊等等&#…

MySQL基础篇(四)事务

一、事务简介 事务是一组操作的集合&#xff0c;它是一个不可分割的工作单位&#xff0c;事务会把所有的操作作为一个整体一期向系统提交或撤销操作请求&#xff0c;即这些操作要么同时成功&#xff0c;要么同时失败。 注意&#xff1a; 默认 MySQL 的事务是 自动提交 的&#…

使用Docker方式安装Artifactory

1、安装前环境准备 首先要关闭防火墙&#xff0c;关闭Selinux&#xff0c;准备好安装好的docker。以下安装版本&#xff1a;7.19.10 ##关闭防火墙&#xff0c;并设置开机自关闭 systemctl stop firewalld.service systemctl disable firewalld.service ##查看防火墙状态 sy…

Spark集群搭建

Spark集群结构 图 名词解释 Driver 该进程调用 Spark 程序的 main 方法&#xff0c;并且启动 SparkContextCluster Manager 该进程负责和外部集群工具打交道&#xff0c;申请或释放集群资源Worker 该进程是一个守护进程&#xff0c;负责启动和管理 ExecutorExecutor 该进程是一…

Linux-故障排查

实验要求 samba仅允许192.168.1.0/24、192.168.10/24进行访问 开一台虚拟机 快照恢复到未联网安装 关闭防火墙 安全linux 编辑ens33网卡 vim /etc/sysconfig/network-scripts/ifcfg-ens33 将ens33网卡复制一份命名为ens37 cp /etc/sysconfig/network-scripts/ifcfg-ens33 /etc…

深度学习课程实验三训练和测试卷积神经网络

一、 实验目的 1、学会搭建、训练和测试卷积神经网络&#xff0c;并掌握其应用。 2、掌握使用numpy实现卷积(CONV)和池化(POOL)层&#xff0c;包括正向春传播和反向传播。 二、 实验步骤 Convolutional Neural Networks: Step by Step 1、导入所需要的安装包 2、构建卷积神经…

Docker安装与仓库使用

日升时奋斗&#xff0c;日落时自省 目录 1、Docker引擎 2、Docker和虚拟机的区别 3、Docker架构 4、Docker安装 4.1、Ubuntu安装 4.1.1、查看版本需求 4.1.2、卸载历史版本 4.1.3、配置docker下载源 4.1.4、自动启动配置 4.1.5、查看docker版本 4.2、CentOS安装 4…

flutter 五:MaterialApp

MaterialApp const MaterialApp({super.key,this.navigatorKey, //导航键this.scaffoldMessengerKey, //scaffold管理this.home, //首页Map<String, WidgetBuilder> this.routes const <String, WidgetBuilder>{}, //路由this.initialRoute, //初始路由th…
最新文章