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

Java设计模式之工厂设计模式

简介

工厂模式是一种常见的设计模式,用于创建对象的过程中,通过工厂类来封装对象的创建过程。其核心思想是将对象的创建和使用分离,从而降低耦合度,提高代码的可维护性和可扩展性。工厂模式通常包括三种类型:简单工厂工厂方法抽象工厂

工厂模式与其他设计模式的主要区别在于,它是一种创建型模式,用于创建对象的过程中,通过工厂类来封装对象的创建过程。与之类似的还有单例模式、建造者模式等。工厂模式主要用于以下两个方面:

  1.     对象的创建和使用分离:将对象的创建过程封装到工厂类中,避免了客户端直接依赖具体的产品类,从而提高了代码的可维护性和可扩展性。
  2.     创建多个产品族或产品等级结构:当需要创建多个产品族或产品等级结构时,工厂模式可以提供一个统一的接口,方便客户端进行调用。
     

什么是简单工厂模式?

简单工厂模式也叫静态工厂模式,属于类的创建型模式。提供一个创建对象实例的功能,而无须关心其具体实现。被创建实例的类型可以是接口、抽象类,也可以是具体的类。 

简单工厂模式的本质:选择实现 。

设计意图: 通过专门定义一个类来负责创建其他类的实例,被创建的实例通常都具有共同的父类。

简单工厂模式的结构

简单工厂模式涉及的角色及其职责如下:

  • 工厂(Factory)角色:简单工厂模式的核心,它负责实现创建所有实例的内部逻辑,该类可以被外界直接调用,创建所需的产品对象。
  • 抽象(Product)角色:简单工厂模式所创建的所有对象的父类,它负责约定所有具体产品类所共有的公共接口。
  • 具体产品(ConcreteProduct)角色:简单工厂模式所创建的真正实例对象。

以具体代码为例: 

现在有两种水果:苹果(Apple)和香蕉(Banana),为了演示,为这两种水果添加一个采集get()方法。 

public class Apple{/** 采集*/public void get(){System.out.println("采集苹果");}
}
public class Banana{/** 采集*/public void get(){System.out.println("采集香蕉");}
}

如果要采集水果,在不使用任何设计模式的情况下,我们一般都会这样做。

public class Client {public static void main(String[] args){//实例化一个AppleApple apple = newApple();//实例化一个BananaBanana banana = newBanana();apple.get();banana.get();}
}

客户端分别创建苹果和香蕉两个具体产品的实例并调用其采集的方法,这样做存在的问题是:客户端需要知道所有的具体水果种类,即客户端与所有的水果类都存在了耦合关系,不符合面向对象设计中的“低耦合”原则。

以上问题如何解决呢?答案:使用简单工厂,利用接口进行“封装隔离”。 

Apple和Banana类既然都是水果,且共有一个采集的get()方法,我们可以为其添加一个父类或让其实现一个共同的接口。在此,我们添加一个Fruit接口。  

public interface Fruit {/** 采集*/public void get();
}

让Apple和Banana类都实现Fruit接口。

public class Apple implements Fruit{/** 采集*/public void get(){System.out.println("采集苹果");}
}
public class Banana implements Fruit{/** 采集*/public void get(){System.out.println("采集香蕉");}
}

接下来创建一个水果工厂类FruitFactory,在工厂中实现对Apple和Banana类的实例化。

public class FruitFactory {/** 获得Apple类的实例*/public static  Fruit getApple(){return new Apple();}/** 获得Banana类实例*/public static Fruit getBanana(){return new Banana();}
}

若FruitFactory中getXXX()方法未声明为static的话,每次调用方法需创建FruitFactory实例比较麻烦,我们直接声明方法为static。

这样,我们就能够使用FruitFactory创建Apple和Banana实例对象了。 

public class Client{public static void main(String[] args){//实例化一个AppleFruit apple = FruitFactory.getApple();Fruit banana = FruitFactory.getBanana();apple.get();banana.get();}
}

观察以上工厂,其为每一个水果具体类都添加了一个getXXX()方法,当水果种类增多时方法数量也会增加导致FruitFactory类比较臃肿,而且各个方法之间关系也不够紧密不符合“高内聚”原则,我们再对其进行优化。 

public class FruitFactory {/** getFruit方法,获得所有产品对象*/public static Fruit getFruit(String type) throws InstantiationException, IllegalAccessException{if(type.equalsIgnoreCase("apple")) {return Apple.class.newInstance();} elseif(type.equalsIgnoreCase("banana")) {return Banana.class.newInstance();} else{System.out.println("找不到相应的实例化类");return null;}}
}

在客户端,使用改良后的FruitFactory来创建Apple和Banana实例对象。

public class Client{public static void main(String[] args){Fruit apple = FruitFactory.getFruit("apple");Fruit banana = FruitFactory.getFruit("banana");apple.get();banana.get();}
}

运行程序打印结果如下: 

采集苹果 
采集香蕉

什么是工厂方法模式?

工厂方法模式同样属于类的创建型模式又被称为多态工厂模式 。工厂方法模式的意义是定义一个创建产品对象的工厂接口,将实际创建工作推迟到子类当中。核心工厂类不再负责产品的创建,这样核心类成为一个抽象工厂角色,仅负责声明具体工厂子类必须实现的接口,这样进一步抽象化的好处是使得工厂方法模式可以使系统在不修改具体工厂角色的情况下引进新的产品。 

模式中包含的角色及其职责

  • 抽象工厂(Factory)角色:工厂方法模式的核心,所有具体工厂类都必须实现这个接口。
  • 具体工厂( ConcreteFactory)角色:具体工厂类是抽象工厂的一个实现,负责实例化具体的产品。
  • 抽象产品(Product)角色:工厂方法模式所创建的所有具体产品的父类,或约定所有具体产品类都应实现的公共接口。
  • 具体产品(ConcreteProduct)角色:工厂方法模式所创建的真正对象。

在简单工厂模式中我们了解了简单工厂模式,我们使用如下具体的工厂FruitFactory对所有的水果对象进行实例化,缺点是显而易见的:当有新品种的水果需要产生时就需要修改工厂的getFruit()方法,加入新品种水果的逻辑判断和业务代码,这极不符合java“开放--封闭”原则。 

public class FruitFactory {/** get方法,获得所有产品对象*/public static Fruit getFruit(String type) throws InstantiationException, IllegalAccessException, ClassNotFoundException {if(type.equalsIgnoreCase("apple")) {return Apple.class.newInstance();} else if(type.equalsIgnoreCase("banana")) {return Banana.class.newInstance();} /** 如果有新品种的水果需要生产需在此处增加相应逻辑判断及业务代码,例如:* else if(type.equalsIgnoreCase("pear")) {*         return Pear.class.newInstance();*    } */else {System.out.println("找不到相应的实例化类");return null;}}
}

接下来我们使用工厂方法模式对以上工厂进行改进,首先工厂方法模式中FruitFactory被设计为了抽象工厂(可以是抽象类或者接口),它不再负责具体的产品创建,而是将产品的创建工作推迟到了由它的子类来实现,接下来以具体的代码为例。

抽象后的FruitFactory仅用来声明其所有子类需实现的接口。

public interface FruitFactory {public Fruit getFruit();
}

有了这个抽象工厂以后,假如我们需要苹果了该怎么办?很简单,创建一个生产苹果的工厂AppleFactory即可。

public class AppleFactory implements FruitFactory{public Fruit getFruit(){return new Apple();}
}

假如我们又需要获取香蕉了该怎么办?依旧简单,创建一个生产香蕉的工厂BananaFactory即可。

public class BananaFactory implements FruitFactory{public Fruit getFruit(){return new Banana();}
}

如果以后还有其他更多新品种的水果加入,只需要创建一个生产相应水果的工厂并让该工厂实现抽象工厂FruitFactory 的接口,需要哪种水果我们就调用相应工厂的getFruit()方法得到水果,这样做有一个好处,无论我们增加多少种水果,我们只需增加一个生产相应水果的工厂即可,无需对现有的代码进行修改,这就很好的符合了"开放--封闭"原则。开闭原则:https://blog.csdn.net/weixin_62458944/article/details/132070314?spm=1001.2014.3001.5501

什么是抽象工厂模式

抽象工厂模式是所有形态的工厂模式中最为抽象和最具一般性的。抽象工厂模式可以向客户端提供一个接口,使得客户端在不必指定产品的具体类型的情况下,能够创建多个产品族的产品对象。

此处引入了一个新的概念产品族,那什么是产品族呢?百度一下:产品族是以产品平台为基础,通过添加不同的个性模块,以满足不同客户个性化需求的一组相关产品。

所谓产品族通俗来说即是:具有某一共性的一系列相关产品.以前面的Apple(苹果),Banana(香蕉),Pear(梨)为例,Apple(苹果),Banana(香蕉),Pear(梨)这三种水果对应上图中的产品等级结构。

这三种水果有产自南方的,也有产自北方的,北方和南方则对应上图中的产品族,产自北方的Apple(苹果),Banana(香蕉),Pear(梨)就构成一个产品族,它们的共性是产自北方,同样产自南方的Apple(苹果),Banana(香蕉),Pear(梨)也构成了一个产品族。 

模式中包含的角色及其职责

  • 抽象工厂(Factory)角色:抽象工厂模式的核心,包含对多个产品等级结构的声明,任何工厂类都必须实现这个接口。
  • 具体工厂(ConcreteFactory)角色:具体工厂类是抽象工厂的一个实现,负责实例化某个产品族中的产品对象。
  • 抽象(Product)角色:抽象模式所创建的所有对象的父类,或声明所有具体产品所共有的公共接口。
  • 具体产品(ConcreteProduct)角色:抽象工厂模式所创建的真正实例。 

总结:抽象工厂中的方法对应产品等级结构,具体工厂对应产品族。

接下来用代码进行说明:保留之前工厂方法模式中的Fruit接口,用来负责描述所有水果实例应该共有的方法。

public interface Fruit {/** 采集*/public void get();
}

此时Apple(苹果)和Banana(香蕉)将不再是具体的产品类而是抽象类,因为它们还需要进一步划分北方和南方两个产品族。

public abstract class Apple implements Fruit{/** 采集*/public abstract void get();
}
public abstract class Banana implements Fruit{/** 采集*/public abstract void get();
}

再进一步细分,苹果(Apple)被具体化为北方苹果(NorthApple)和南方苹果(SouthApple)。

public class NorthApple extends Apple {public void get() {System.out.println("采集北方苹果");}}
public class SouthApple extends Apple {public void get() {System.out.println("采集南方苹果");}}

香蕉(Banana)被具体化为北方香蕉(NorthBanana)和南方香蕉(SouthBanana).

public class NorthBanana extends Banana {public void get() {System.out.println("采集北方香蕉");}}
public class SouthBanana extends Banana {public void get() {System.out.println("采集南方香蕉");}}

继续写工厂,与之前的FruitFactory有所不同,此时的FruitFactory需为每一个产品等级结构添加获取方法声明。

public interface FruitFactory {//实例化Applepublic Fruit getApple();//实例化Bananapublic Fruit getBanana();
}

为每一个产品族添加相应的工厂,NorthFruitFactory负责生产所有北方的水果,SouthFruitFactory负责生产所有南方的水果。

public class NorthFruitFactory implements FruitFactory {public Fruit getApple() {return new NorthApple();}public Fruit getBanana() {return new NorthBanana();}}
public class SouthFruitFactory implements FruitFactory {public Fruit getApple() {return new SouthApple();}public Fruit getBanana() {return new SouthBanana();}}

在客户端中进行测试,代码如下。

public class Client {public static void main(String[] args) {FruitFactory ff1 = new NorthFruitFactory();Fruit apple1 = ff1.getApple();apple1.get();Fruit banana1 = ff1.getBanana();banana1.get();FruitFactory ff2 = new SouthFruitFactory();Fruit apple2 = ff2.getApple();apple2.get();Fruit banana2 = ff2.getBanana();banana2.get();}
}

运行程序打印结果如下:

采集北方苹果
采集北方香蕉
采集南方苹果
采集南方香蕉

工厂模式的优缺点

优点

  • 封装对象的创建过程:工厂模式将对象的创建过程封装到工厂类中,避免了客户端直接依赖具体的产品类,从而提高了代码的可维护性和可扩展性。
  •  创建多个产品族或产品等级结构:当需要创建多个产品族或产品等级结构时,工厂模式可以提供一个统一的接口,方便客户端进行调用。
  •  符合开闭原则:当需要添加新的产品时,只需要增加相应的产品类和工厂方法即可,不需要修改原有的代码,符合开闭原则。

缺点

  1.  增加代码复杂度:工厂模式需要增加额外的工厂类,增加了代码的复杂度。
  2.  增加系统的抽象性和理解难度:由于工厂模式引入了抽象层,因此增加了系统的抽象性和理解难度。

工厂模式运用场景

工厂模式适用于以下场景:

  1. 需要创建多个产品族或产品等级结构:当需要创建多个产品族或产品等级结构时,工厂模式可以提供一个统一的接口,方便客户端进行调用。
  2. 需要封装对象的创建过程:当对象的创建过程比较复杂,或者需要依赖其他类的时候,可以使用工厂模式来封装对象的创建过程。
  3. 需要动态切换产品:当需要动态切换产品时,工厂模式可以提供一个统一的接口,方便客户端进行调用。

总结

工厂模式是一种常见的设计模式,用于创建对象的过程中,通过工厂类来封装对象的创建过程。工厂模式具有封装对象的创建过程、创建多个产品族或产品等级结构、符合开闭原则等优点,同时也存在增加代码复杂度、增加系统的抽象性和理解难度等缺点。在实际的开发中,我们可以根据具体的需求来选择使用工厂模式或其他设计模式。

相关文章:

Java设计模式之工厂设计模式

简介 工厂模式是一种常见的设计模式,用于创建对象的过程中,通过工厂类来封装对象的创建过程。其核心思想是将对象的创建和使用分离,从而降低耦合度,提高代码的可维护性和可扩展性。工厂模式通常包括三种类型:简单工厂…...

uniapp使用阿里图标

效果图: 前言 随着uniApp的深入人心,我司也陆续做了几个使用uniapp做的移动端跨平台软件,在学习使用的过程中深切的感受到了其功能强大和便捷,今日就如何在uniapp项目中使用阿里字体图标的问题为大家献上我的一点心得&#xff0…...

20230803激活手机realme GT Neo3

20230803激活手机realme GT Neo3 缘起: 新买的手机:realme GT Neo3 需要确认: 1、4K录像,时间不限制。 【以前的很多手机都是限制8/10/12/16分钟】 2、通话自动录音 3、定时开关机。 4、GPS记录轨迹不要拉直线:户外助…...

Spring Cloud Feign+Ribbon的超时机制

在一个项目中(数据产品),需要对接企业微信中第三方应用。在使用 Feign 的去调用微服务的用户模块用微信的 code 获取 access_token 以及用户工厂信息时出现 Feign 重试超时报错的情况,通过此篇文章记录问题解决的过程。 一、问题重…...

使用docker 搭建nginx + tomcat 集群

创建3个Tomcat容器,端口分别映射到 8080,8081,8082,使用数据卷挂载,分别将宿主机目录下的 /opt/module/docker/tomcat3/ROOT1/,/opt/module/docker/tomcat3/ROOT2/,/opt/module/docker/tomcat3/ROOT2/ 挂载到 容器内部…...

从Spring的角度看Memcached和Redis及操作

目录 Memcached和Redis的区别 适用场景 Memcached配置使用 Redis配置使用 在SpringBoot的框架里,有直连Redis的SDK却没有Memcached的,可见相比地位。不过各有各的适应场景,Redis这个单线程模型确实非常强。 Memcached和Redis的区别 共同…...

【C语言学习】C语言的基础数据类型

一、数据类型 1.整型 short(短整型) int(整型 long(长整型) long long(长整型)2.浮点型 float(单精度型) double(双精度型) long double3.字符类型 char…...

使用AIGC工具提升安全工作效率

新钛云服已累计为您分享760篇技术干货 在日常工作中,安全人员可能会涉及各种各样的安全任务,包括但不限于: 开发某些安全工具的插件,满足自己特定的安全需求;自定义github搜索工具,快速查找所需的安全资料、…...

HBase概述

HBase 一 HBase简介与环境部署 1.1 HBase简介&在Hadoop生态中的地位 1.1.1 什么是HBase HBase是一个分布式的、面向列的开源数据库HBase是Google BigTable的开源实现HBase不同于一般的关系数据库, 适合非结构化数据存储 1.1.2 BigTable BigTable是Google设计的分布式…...

el-popover全屏不显示(bug记录)

我做了一个el-table全屏展示的功能, 然后里面的el-popover在全屏后无法展示, 刚开始以为是写唯一的key或者ref, 发现写了也不行, 后来以为要写’:append-to-body“false”, 最后发现是我的外层的层级写得太高了; position: fixed; z-index: 9999; <div class"box"…...

react中使用redux-persist做持久化储存

某天下午折腾着玩的 – 笔记 安装相关依赖 npm install reduxjs/toolkit redux-persist redux react-redux// store.jsx import { configureStore, getDefaultMiddleware } from "reduxjs/toolkit"; import { persistStore, persistReducer } from "redux-per…...

【leetcode】203. 移除链表元素(easy)

给你一个链表的头节点 head 和一个整数 val &#xff0c;请你删除链表中所有满足 Node.val val 的节点&#xff0c;并返回 新的头节点 。 /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}* Lis…...

InfiniBand、UCIe相关思考

InfiniBand、UCIe相关思考 内容1、InfiniBandInfiniBand是什么&#xff1f;InfiniBand的来历是什么&#xff1f;InfiniBand为什么重要&#xff1f;InfiniBand相较于Ethernet区别&#xff1f;同领域内还有其他哪些技术&#xff1f;InfiniBand中RDMA是种什么技术&#xff1f; 内容…...

[C++项目] Boost文档 站内搜索引擎(3): 建立文档及其关键字的正排 倒排索引、jieba库的安装与使用...

之前的两篇文章: 第一篇文章介绍了本项目的背景, 获取了Boost库文档 &#x1fae6;[C项目] Boost文档 站内搜索引擎(1): 项目背景介绍、相关技术栈、相关概念介绍…第二篇文章 分析实现了parser模块. 此模块的作用是 对所有文档html文件, 进行清理并汇总 &#x1fae6;[C项目] …...

el-date-picker回显问题解决记录

el-date-picker回显问题记录 组件结构 <el-date-pickerv-model"time"type"datetimerange"range-separator"至"start-placeholder"开始日期"end-placeholder"结束日期"value-format"yyyy-MM-dd HH:mm:ss":defau…...

Linux中的特殊进程(孤儿进程、僵尸进程、守护进程)

一、孤儿进程 1&#xff09;父进程退出&#xff0c;子进程不退出&#xff0c;此时子进程被1号&#xff08;init&#xff09;进程收养&#xff0c;变成孤儿进程。 2&#xff09;孤儿进程会脱离终端控制&#xff0c;且运行在后端&#xff0c;不能用ctrlc杀死后端进程&#xff0c;…...

【分布式能源选址与定容】光伏、储能双层优化配置接入配电网研究(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f389;3 参考文献 &#x1f308;4 Matlab代码、数据、讲解 &#x1f4a5;1 概述 由于能源的日益匮乏&#xff0c;电力需求的不断增长等&#xff0c;配电网中分布式能源渗透率不断提高&#xff0c;且逐渐向主动配电网方…...

《cuda c编程权威指南》04 - 使用块和线程索引映射矩阵索引

目录 1. 解决的问题 2. 分析 3. 方法 4. 代码示例 1. 解决的问题 利用块和线程索引&#xff0c;从全局内存中访问指定的数据。 2. 分析 通常情况下&#xff0c;矩阵是用行优先的方法在全局内存中线性存储的。如下。 8列6行矩阵&#xff08;nx,ny&#xff09;&#xff08;…...

mysql 、sql server 常见的区别

&#xff2e;&#xff35;&#xff2c;&#xff2c;   处理 MySQL IFNULL(col , val) SQL Server ISNULL(col,val) 表名、列名等 一般不推荐用保留字 &#xff0c;如果非要保留字 MySQL 用用着重号&#xff0c;即 反引号 包括 select col from GROUP SQL Server 用用着重号…...

查找特定元素——C++ 算法库(std::find_if)

std::find_if函数在C++中的实际使用案例非常广泛,以下是一些常见的用法示例: 1、在容器中查找满足特定条件的元素: #include <iostream> #include <vector> #include <algorithm>bool isOdd(int num) {...

微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】

微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来&#xff0c;Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...

【位运算】消失的两个数字(hard)

消失的两个数字&#xff08;hard&#xff09; 题⽬描述&#xff1a;解法&#xff08;位运算&#xff09;&#xff1a;Java 算法代码&#xff1a;更简便代码 题⽬链接&#xff1a;⾯试题 17.19. 消失的两个数字 题⽬描述&#xff1a; 给定⼀个数组&#xff0c;包含从 1 到 N 所有…...

深入理解JavaScript设计模式之单例模式

目录 什么是单例模式为什么需要单例模式常见应用场景包括 单例模式实现透明单例模式实现不透明单例模式用代理实现单例模式javaScript中的单例模式使用命名空间使用闭包封装私有变量 惰性单例通用的惰性单例 结语 什么是单例模式 单例模式&#xff08;Singleton Pattern&#…...

【Redis】笔记|第8节|大厂高并发缓存架构实战与优化

缓存架构 代码结构 代码详情 功能点&#xff1a; 多级缓存&#xff0c;先查本地缓存&#xff0c;再查Redis&#xff0c;最后才查数据库热点数据重建逻辑使用分布式锁&#xff0c;二次查询更新缓存采用读写锁提升性能采用Redis的发布订阅机制通知所有实例更新本地缓存适用读多…...

uniapp 开发ios, xcode 提交app store connect 和 testflight内测

uniapp 中配置 配置manifest 文档&#xff1a;manifest.json 应用配置 | uni-app官网 hbuilderx中本地打包 下载IOS最新SDK 开发环境 | uni小程序SDK hbulderx 版本号&#xff1a;4.66 对应的sdk版本 4.66 两者必须一致 本地打包的资源导入到SDK 导入资源 | uni小程序SDK …...

Elastic 获得 AWS 教育 ISV 合作伙伴资质,进一步增强教育解决方案产品组合

作者&#xff1a;来自 Elastic Udayasimha Theepireddy (Uday), Brian Bergholm, Marianna Jonsdottir 通过搜索 AI 和云创新推动教育领域的数字化转型。 我们非常高兴地宣布&#xff0c;Elastic 已获得 AWS 教育 ISV 合作伙伴资质。这一重要认证表明&#xff0c;Elastic 作为 …...

【Veristand】Veristand环境安装教程-Linux RT / Windows

首先声明&#xff0c;此教程是针对Simulink编译模型并导入Veristand中编写的&#xff0c;同时需要注意的是老用户编译可能用的是Veristand Model Framework&#xff0c;那个是历史版本&#xff0c;且NI不会再维护&#xff0c;新版本编译支持为VeriStand Model Generation Suppo…...

链式法则中 复合函数的推导路径 多变量“信息传递路径”

非常好&#xff0c;我们将之前关于偏导数链式法则中不能“约掉”偏导符号的问题&#xff0c;统一使用 二重复合函数&#xff1a; z f ( u ( x , y ) , v ( x , y ) ) \boxed{z f(u(x,y),\ v(x,y))} zf(u(x,y), v(x,y))​ 来全面说明。我们会展示其全微分形式&#xff08;偏导…...

Python爬虫实战:研究Restkit库相关技术

1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的有价值数据。如何高效地采集这些数据并将其应用于实际业务中,成为了许多企业和开发者关注的焦点。网络爬虫技术作为一种自动化的数据采集工具,可以帮助我们从网页中提取所需的信息。而 RESTful API …...

如何把工业通信协议转换成http websocket

1.现状 工业通信协议多数工作在边缘设备上&#xff0c;比如&#xff1a;PLC、IOT盒子等。上层业务系统需要根据不同的工业协议做对应开发&#xff0c;当设备上用的是modbus从站时&#xff0c;采集设备数据需要开发modbus主站&#xff1b;当设备上用的是西门子PN协议时&#xf…...