当前位置: 首页 > 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) {...

Java 语言特性(面试系列1)

一、面向对象编程 1. 封装&#xff08;Encapsulation&#xff09; 定义&#xff1a;将数据&#xff08;属性&#xff09;和操作数据的方法绑定在一起&#xff0c;通过访问控制符&#xff08;private、protected、public&#xff09;隐藏内部实现细节。示例&#xff1a; public …...

基于Flask实现的医疗保险欺诈识别监测模型

基于Flask实现的医疗保险欺诈识别监测模型 项目截图 项目简介 社会医疗保险是国家通过立法形式强制实施&#xff0c;由雇主和个人按一定比例缴纳保险费&#xff0c;建立社会医疗保险基金&#xff0c;支付雇员医疗费用的一种医疗保险制度&#xff0c; 它是促进社会文明和进步的…...

STM32F4基本定时器使用和原理详解

STM32F4基本定时器使用和原理详解 前言如何确定定时器挂载在哪条时钟线上配置及使用方法参数配置PrescalerCounter ModeCounter Periodauto-reload preloadTrigger Event Selection 中断配置生成的代码及使用方法初始化代码基本定时器触发DCA或者ADC的代码讲解中断代码定时启动…...

376. Wiggle Subsequence

376. Wiggle Subsequence 代码 class Solution { public:int wiggleMaxLength(vector<int>& nums) {int n nums.size();int res 1;int prediff 0;int curdiff 0;for(int i 0;i < n-1;i){curdiff nums[i1] - nums[i];if( (prediff > 0 && curdif…...

解决本地部署 SmolVLM2 大语言模型运行 flash-attn 报错

出现的问题 安装 flash-attn 会一直卡在 build 那一步或者运行报错 解决办法 是因为你安装的 flash-attn 版本没有对应上&#xff0c;所以报错&#xff0c;到 https://github.com/Dao-AILab/flash-attention/releases 下载对应版本&#xff0c;cu、torch、cp 的版本一定要对…...

Android15默认授权浮窗权限

我们经常有那种需求&#xff0c;客户需要定制的apk集成在ROM中&#xff0c;并且默认授予其【显示在其他应用的上层】权限&#xff0c;也就是我们常说的浮窗权限&#xff0c;那么我们就可以通过以下方法在wms、ams等系统服务的systemReady()方法中调用即可实现预置应用默认授权浮…...

CMake控制VS2022项目文件分组

我们可以通过 CMake 控制源文件的组织结构,使它们在 VS 解决方案资源管理器中以“组”(Filter)的形式进行分类展示。 🎯 目标 通过 CMake 脚本将 .cpp、.h 等源文件分组显示在 Visual Studio 2022 的解决方案资源管理器中。 ✅ 支持的方法汇总(共4种) 方法描述是否推荐…...

用机器学习破解新能源领域的“弃风”难题

音乐发烧友深有体会&#xff0c;玩音乐的本质就是玩电网。火电声音偏暖&#xff0c;水电偏冷&#xff0c;风电偏空旷。至于太阳能发的电&#xff0c;则略显朦胧和单薄。 不知你是否有感觉&#xff0c;近两年家里的音响声音越来越冷&#xff0c;听起来越来越单薄&#xff1f; —…...

C++.OpenGL (14/64)多光源(Multiple Lights)

多光源(Multiple Lights) 多光源渲染技术概览 #mermaid-svg-3L5e5gGn76TNh7Lq {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-3L5e5gGn76TNh7Lq .error-icon{fill:#552222;}#mermaid-svg-3L5e5gGn76TNh7Lq .erro…...

面向无人机海岸带生态系统监测的语义分割基准数据集

描述&#xff1a;海岸带生态系统的监测是维护生态平衡和可持续发展的重要任务。语义分割技术在遥感影像中的应用为海岸带生态系统的精准监测提供了有效手段。然而&#xff0c;目前该领域仍面临一个挑战&#xff0c;即缺乏公开的专门面向海岸带生态系统的语义分割基准数据集。受…...