五、工厂方法模式

文章目录

  • 1 基本介绍
  • 2 案例
    • 2.1 Drink 抽象类
    • 2.2 Tea 类
    • 2.3 Coffee 类
    • 2.4 DrinkFactory 抽象类
    • 2.5 TeaFactory 类
    • 2.6 CoffeeFactory 类
    • 2.7 Client 类
    • 2.8 Client 类运行结果
    • 2.9 总结
  • 3 各角色之间的关系
    • 3.1 角色
      • 3.1.1 Product ( 抽象产品 )
      • 3.1.2 ConcreteProduct ( 具体产品 )
      • 3.1.3 Factory ( 抽象工厂 )
      • 3.1.4 ConcreteFactory ( 具体工厂 )
      • 3.1.5 Client ( 客户端 )
    • 3.2 类图
  • 4 注意事项
  • 5 在源码中的使用
  • 6 优缺点
  • 7 适用场景
  • 8 总结


1 基本介绍

工厂方法模式(Factory Method Pattern)是一种 创建型 设计模式,它 定义了一个创建对象的 接口(抽象方法),但 让子类决定要实例化的类是哪一个

它不同于 简单工厂模式,工厂方法模式将对象的创建逻辑分散到各个具体的工厂类中,而不是集中在一个工厂类中。

它还不同于 抽象工厂模式,工厂方法模式在创建产品对象时有 相似的额外操作,所以需要给外界提供一个方法,并强制子类实现另一个方法。

2 案例

本模式的案例 和 简单工厂模式 的案例很相似,只不过将其中的单个工厂改为了 一个抽象工厂类 + 多个工厂实现类。如下所示:

  • 饮品:Drink 抽象类有一个抽象方法 drink(),代表喝这个饮品。它有两个子类:Tea 类和 Coffee 类。
  • 工厂:DrinkFactory 抽象类有两个方法:第一个方法是获取已创建的饮品数,第二个方法能够根据饮品的类型生产对应的饮品。它有两个实现类,分别为 TeaFactory 类和 CoffeeFactory 类。
  • 客户端:Client 类中使用了不同的饮品工厂,创建了不同类型的饮品。

2.1 Drink 抽象类

public abstract class Drink { // 饮品的抽象类public abstract void drink(); // 喝饮品protected String name; // 饮品的名称public Drink(String name) {this.name = name;}public String getName() {return name;}
}

2.2 Tea 类

public class Tea extends Drink { // 茶饮品public Tea(String name) {super(name);}@Overridepublic void drink() {System.out.println("你喝了[" + super.name + "]这个茶饮品");}
}

2.3 Coffee 类

public class Coffee extends Drink { // 咖啡饮品public Coffee(String name) {super(name);}@Overridepublic void drink() {System.out.println("你喝了[" + super.name + "]这个咖啡饮品");}
}

2.4 DrinkFactory 抽象类

public abstract class DrinkFactory  { // 饮品工厂抽象类private static final AtomicInteger count = new AtomicInteger(0); // 统计创建的饮品个数// 获取创建的饮品个数public static int getDrinkCount() {return count.get();}// 创建 指定名称 的饮品,被外部的类调用public final Drink create(String drinkName) {count.getAndIncrement();return createDrink(drinkName);}// 创建 指定名称 的饮品,留给子类实现,不应该被外部的类调用protected abstract Drink createDrink(String drinkName);
}

2.5 TeaFactory 类

public class TeaFactory extends DrinkFactory { // 茶饮品工厂类@Overrideprotected Drink createDrink(String drinkName) {return new Tea(drinkName); // 创建 茶饮品 的对象}
}

2.6 CoffeeFactory 类

public class CoffeeFactory extends DrinkFactory { // 咖啡饮品工厂类@Overrideprotected Drink createDrink(String drinkName) {return new Coffee(drinkName); // 创建 咖啡饮品 的对象}
}

2.7 Client 类

// 最好让 Client 与 DrinkFactory 在不同的包下,这样可以避免 Client 误调用 createProduct() 方法
public class Client { // 使用 具体饮品工厂创建具体饮品 的客户端public static void main(String[] args) {DrinkFactory teaFactory = new TeaFactory();Drink tea = teaFactory.create("铁观音");tea.drink();DrinkFactory coffeeFactory = new CoffeeFactory();Drink coffee = coffeeFactory.create("拿铁");coffee.drink();System.out.println("已经创建了 " + DrinkFactory.getDrinkCount() + " 个饮品");}
}

2.8 Client 类运行结果

你喝了[铁观音]这个茶饮品
你喝了[拿铁]这个咖啡饮品
已经创建了 2 个饮品

2.9 总结

在本模式的案例中,工厂类比 简单工厂模式 多;在初始化工厂时,需要初始化具体工厂的对象;在生产产品时,只需要传入与对象有关的参数,不需要传入与对象类型有关的信息。

如果想要新增一种具体产品,则只需写 具体产品类其对应的具体工厂类 的代码,而不需要修改 抽象工厂类,这样就 遵守了开闭原则提高了系统的扩展性

此外,本模式还有简单工厂模式的优点——降低系统之间的耦合度,这是因为在本模式中,客户端也不需要知道具体产品的创建逻辑,只要会使用工厂即可。

3 各角色之间的关系

3.1 角色

3.1.1 Product ( 抽象产品 )

该角色负责 定义 所有具体产品的 共性(包括 属性 和 方法),是所有具体产品的 父类 或 被实现的接口。在本案例中,Drink 抽象类扮演了这个角色。

3.1.2 ConcreteProduct ( 具体产品 )

该角色负责 实现 抽象产品所定义的 方法。在本案例中,Tea 类和 Coffee 类都在扮演这个角色。

3.1.3 Factory ( 抽象工厂 )

该角色负责 定义 创建产品对象的 抽象方法,并 统一处理创建产品对象的额外操作。在本案例中,DrinkFactory 抽象类扮演了这个角色。

3.1.4 ConcreteFactory ( 具体工厂 )

该角色负责 实现 创建产品对象的 抽象方法,而且只负责创建某一种具体产品。在本案例中,TeaFactory 类和 CoffeeFactory 类都在扮演这个角色。

3.1.5 Client ( 客户端 )

该角色负责 使用 具体工厂创建具体产品。在本案例中,Client 类扮演了这个角色。

3.2 类图

alt text
说明:ConcreteProduct 类和 ConcreteFactory 类是一一对应的,它们两个的关系和 Product 抽象类与 Factory 抽象类的关系是一致的。此外,工厂的 createProduct() 方法的访问权限是 protected,即仅允许 本包的类 或 子类 使用,并且被 abstract 修饰,强制子类重写此方法;create() 方法的访问权限是 public,所以可以被外部的类使用。

4 注意事项

本模式的注意事项与 抽象工厂模式 的十分相似,共有以下几点:

  • 考虑系统复杂度:使用工厂方法模式需要引入一个或多个工厂类,这可能会增加系统的复杂度,在决定是否使用此模式时,需要权衡其带来的好处和增加的复杂度。
  • 共同基类或接口:所有产品类必须有一个共同的基类或接口,以便 create() 方法能够返回一个 通用 的产品类型,这有助于保持系统的灵活性和可扩展性。
  • 对象重用:在某些情况下,为了 提高性能减少资源消耗create() 方法可能会返回 缓存的对象对象池中的对象,而不是每次都创建新对象,这需要根据具体的应用场景和需求来决定。
  • 扩展具体产品:当需要新增产品时,只需新增产品类和相应的工厂类。
  • 考虑使用接口:在可能的情况下,将抽象产品角色定义为 接口 而不是 抽象类,可以提高系统的灵活性和扩展性。在 Java 中,一个类只能继承一个父类,但能实现多个接口。
  • 考虑使用抽象工厂模式:如果在创建产品对象时有相似的额外操作,则使用本模式比较好,但是,如果没有相似的额外操作,最好使用 抽象工厂模式。

5 在源码中的使用

在 Spring 框架中,工厂模式被广泛应用于对象的创建和管理过程中。

Spring 的 AbstractApplicationContext 抽象类是工厂方法模式的典型应用,它 定义 了多个 getBean() 方法,允许客户端通过传入不同的条件(如 Bean 的名称或类型)来获取对应的 Bean 实例。

// 在 org.springframework.context.support.AbstractApplicationContext 抽象类中有如下几个方法
@Override
public Object getBean(String name) throws BeansException {assertBeanFactoryActive();return getBeanFactory().getBean(name);
}@Override
public <T> T getBean(String name, Class<T> requiredType) throws BeansException {assertBeanFactoryActive();return getBeanFactory().getBean(name, requiredType);
}@Override
public <T> T getBean(Class<T> requiredType) throws BeansException {assertBeanFactoryActive();return getBeanFactory().getBean(requiredType);
}// 检查 Bean 是否活跃
protected void assertBeanFactoryActive() {if (!this.active.get()) {if (this.closed.get()) {throw new IllegalStateException(getDisplayName()+ " has been closed already");} else {throw new IllegalStateException(getDisplayName()+ " has not been refreshed yet");}}
}@Override
public abstract ConfigurableListableBeanFactory getBeanFactory()throws IllegalStateException;

这三个 getBean() 方法都检查 Bean 是否活跃(额外操作),并且还调用了 abstract 方法 getBeanFactory(),这个方法由其子类实现,例如 AbstractRefreshableApplicationContext 抽象类。

6 优缺点

优点

  • 扩展性好:当需要新增产品时,只需新增产品类和相应的工厂类,无需修改原有的工厂抽象类和工厂类,满足开闭原则。
  • 职责单一:每个工厂类只负责创建对应的产品,满足单一职责原则。
  • 降低耦合度:客户端通过工厂抽象类与具体产品解耦,不需要知道产品的具体实现细节,只需要知道如何获取所需的产品。
  • 灵活性高:可以根据不同的应用场景或条件选择不同的工厂子类来创建产品,增加了系统的灵活性。

缺点

  • 增加了系统的复杂度:每增加一种产品,就需要增加一个对应的产品类和一个对应的工厂子类,这会增加系统中类的数量,从而增加系统的复杂度。
  • 增加了系统的抽象层次:工厂方法模式引入了 工厂抽象类 和 工厂子类,增加了系统的抽象层次,使得系统的理解和维护变得 相对 复杂。
  • 过度设计:在某些情况下,如果系统中产品的 种类不多,或者产品之间的 差异性不大,使用工厂方法模式可能会导致过度设计,增加了不必要的复杂性。这时使用 简单工厂模式 会好一点。
  • 依赖性问题:如果工厂类依赖于其他资源或配置,那么这些依赖项的管理和配置可能会变得复杂。特别是在大型系统中,工厂类的依赖关系可能会形成一个复杂的网络,增加了系统的维护难度。

7 适用场景

  • 多种产品族的创建:当系统中存在多个产品族,并且这些产品族之间存在公共的接口或抽象类,但每个产品族的具体实现不同时,可以使用工厂方法模式。每个产品族对应一个工厂类,负责创建该族中的具体产品对象。
  • 系统需要扩展但不想修改已有代码:当系统需要添加新的产品类型时,如果希望在不修改已有代码的基础上实现扩展,工厂方法模式是一个很好的选择。
  • 复杂的对象创建:当对象的创建过程比较复杂,包含多个步骤或者需要依赖其他对象时,使用工厂方法模式可以将对象的创建过程封装在工厂类中,使得客户端代码更加简洁和易于理解。
  • 隐藏具体产品的创建细节:在某些情况下,我们可能不希望客户端代码知道具体产品的创建细节,或者希望 隐藏 这些细节以简化客户端的使用。工厂方法模式可以通过提供一个共同的接口来隐藏具体产品的创建细节,使得客户端只需通过接口来创建对象。
  • 使用第三方类库:当系统需要使用第三方类库来创建对象,并且这些对象的创建过程比较复杂或者需要遵循特定的规则时,可以使用工厂方法模式来封装这些创建过程,以便在系统中更加方便地使用这些对象。
  • 设计考虑:在系统设计初期,如果预见到将来可能需要添加新的产品类型或者对产品类型进行扩展,可以考虑使用工厂方法模式来为未来可能的扩展 预留接口。这样可以使得系统在扩展时更加灵活和方便。
  • 在创建产品对象时有相似的额外操作:如果创建产品对象时有相似的额外操作,那么就使用工厂方法模式,将相似的额外操作放到 提供给外界的 创建产品对象方法中,让工厂子类只实现初始化产品对象的逻辑。

8 总结

工厂方法模式是一种 创建型 设计模式,它通过多个具体工厂类来创建具体产品,这些具体产品通常具有共同的抽象产品作为父类或接口,这些具体工厂类也有共同的抽象工厂作为 父类

工厂方法模式将具体产品的创建逻辑 封装 在各个工厂类中,并 将相 似的额外操作 封装到对外提供的方法中,客户端无需知道具体产品的类名,只需要知道相应的工厂即可,从而 降低了 客户端与具体产品类之间的 耦合度

与简单工厂模式相比,工厂方法模式对具体产品的 种类不做限制,并且具体产品的 创建逻辑也可以很复杂 。在扩充产品类型时,该模式不需要修改代码,只需要添加代码,满足 开闭原则。每个具体工厂类只负责创建一种产品,满足 单一职责原则

与抽象工厂模式相比,工厂方法模式 处理了创建产品对象时相似的额外逻辑,但是在 Java 中,由于单继承,所以在 灵活性 方面,使用抽象类(工厂方法模式) 比 使用接口(抽象工厂模式) 差一些

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

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

相关文章

生物信息学新突破:在英特尔 Gaudi 2 上实现 ProtST 蛋白质语言模型加速

引言 随着人工智能技术的快速发展&#xff0c;蛋白质结构预测和语言模型在生物信息学领域扮演着越来越重要的角色。ProtST作为一种新兴的蛋白质语言模型&#xff0c;其性能在英特尔 Gaudi 2 加速器的助力下得到了显著提升。本文将探讨如何利用英特尔 Gaudi 2 加速 ProtST 模型…

哈希表相关的力扣题和讲解和Java、C++常用的数据结构(哈希法)

20240725 一、什么时候适用什么样的结构。1.java中1.1 HashSet&#xff1a;1.2 TreeSet&#xff1a;1.3 LinkedHashSet&#xff1a;1.4 HashMap&#xff1a;1.5 TreeMap&#xff1a;1.6 LinkedHashMap&#xff1a;1.7 总结 2. c中2.1 std::unordered_set&#xff1a;2.2 std::s…

项目实战——外挂开发(30小时精通C++和外挂实战)

项目实战——外挂开发&#xff08;30小时精通C和外挂实战&#xff09; 外挂开发1-监控游戏外挂开发2-秒杀僵尸外挂开发3-阳光地址分析外挂开发4-模拟阳光外挂开发5-无限阳光 外挂开发1-监控游戏 外挂的本质 有两种方式 1&#xff0c;修改内存中的数据 2&#xff0c;更改内存中…

谷粒商城实战笔记-54-商品服务-API-三级分类-拖拽效果

文章目录 一&#xff0c;54-商品服务-API-三级分类-修改-拖拽效果1&#xff0c;el-tree控件加上允许拖拽的属性2&#xff0c;是否允许拖拽3&#xff0c;完整代码 一&#xff0c;54-商品服务-API-三级分类-修改-拖拽效果 本节的主要内容是给三级分类树形结构加上拖拽功能&#…

Mysql 集群搭建 05

文章目录 1. Mysql主从复制集群搭建1.1 主库配置1.2 从库配置 2. 分库分表2.1 拆分策略2.2 实现技术2.2.1 MyCat概述2.2.2 MyCat入门2.2.3 配置 schema.xml 3. 双主双从4. 双主双从读写分离 1. Mysql主从复制集群搭建 主从复制是指将主数据库的 DDL 和 DML 操作通过二进制日志…

VMware Cloud Foundation ESXi 主机

一、准备嵌套 ESXi 主机环境# 1)物理 ESXi 主机信息 本次准备用于部署 VCF 嵌套实验环境的物理宿主机的配置信息如下图所示。其实,部署 VCF 环境主要对内存的大小要求比较高,部署完整的管理域相关组件下来差不多就要占用 200 GB左右内存,而对 CPU 和存储的需求可以根据实…

Pytorch使用教学8-张量的科学运算

在介绍完PyTorch中的广播运算后&#xff0c;继续为大家介绍PyTorch的内置数学运算&#xff1a; 首先对内置函数有一个功能印象&#xff0c;知道它的存在&#xff0c;使用时再查具体怎么用其次&#xff0c;我还会介绍PyTorch科学运算的注意事项与一些实用小技巧 1 基本数学运算…

idea中项目目录,文件显示不全问题

问题&#xff1a;idea中项目目录显示不全问题 解决办法1&#xff1a; 删除目录中的.idea文件 用idea重新打开文件就行了 办法2&#xff1a;手动导入为maven项目 1. 2. 3. 4.选择要导入的项目&#xff0c;导入为maven

在英特尔 Gaudi 2 上加速蛋白质语言模型 ProtST

引言 蛋白质语言模型 (Protein Language Models, PLM) 已成为蛋白质结构与功能预测及设计的有力工具。在 2023 年国际机器学习会议 (ICML) 上&#xff0c;MILA 和英特尔实验室联合发布了ProtST模型&#xff0c;该模型是个可基于文本提示设计蛋白质的多模态模型。此后&#xff0…

昇思25天学习打卡营第22天|Pix2Pix实现图像转换

Pix2Pix图像转换学习总结 概述 Pix2Pix是一种基于条件生成对抗网络&#xff08;cGAN&#xff09;的深度学习模型&#xff0c;旨在实现不同图像风格之间的转换&#xff0c;如从语义标签到真实图像、灰度图到彩色图、航拍图到地图等。这一模型由Phillip Isola等人在2017年提出&…

编写Hello World!,开启cpp人生

一、具体步骤 1.、安装Visual Studio2019(网上教程很多&#xff09;并打开。 2、新建项目 首先配置新项目 其中 解决方案包含项目 然后添加cpp文件并编写代码 代码如下 #include <iostream> using namespace std; int main() {cout << "Hello World~&qu…

electron TodoList网页应用打包成linux deb、AppImage应用

这里用的是windows的wsl的ubuntu环境 electron应用打包linux应用需要linux下打包&#xff0c;这里用windows的wsl的ubuntu环境进行操作 1&#xff09;linux ubuntu安装nodejs、electron 安装nodejs&#xff1a; sudo apt update sudo apt upgrade ##快捷安装 curl -fsSL http…

机器学习驱动的智能化电池管理技术与应用

目录 主要内容 电池管理技术概述 电池的工作原理与关键性能指标 电池管理系统的核心功能 SOC估计 SOH估计 寿命预测 故障诊断 人工智能机器学习 基础 人工智能的发展 机器学习的关键概念 机器学习在电池管理中的应用案例介绍 人工智能在电池荷电状态估计中的…

小猪佩奇.js

闲着没事 使用js 画一个小猪佩奇把 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</tit…

UDP/TCP协议解析

我最近开了几个专栏&#xff0c;诚信互三&#xff01; > |||《算法专栏》&#xff1a;&#xff1a;刷题教程来自网站《代码随想录》。||| > |||《C专栏》&#xff1a;&#xff1a;记录我学习C的经历&#xff0c;看完你一定会有收获。||| > |||《Linux专栏》&#xff1…

Elasticsearch:跨集群使用 ES|QL

警告&#xff1a;ES|QL 的跨集群搜索目前处于技术预览阶段&#xff0c;可能会在未来版本中更改或删除。Elastic 将努力解决任何问题&#xff0c;但技术预览中的功能不受官方 GA 功能的支持 SLA 约束。 使用 ES|QL&#xff0c;你可以跨多个集群执行单个查询。 前提&#xff1a; …

实战解读:Llama Guard 3 Prompt Guard

前序研究&#xff1a;实战解读&#xff1a;Llama 3 安全性对抗分析 近日&#xff0c;腾讯朱雀实验室又针对 Llama 3.1 安全性做了进一步解读。 2024年7月23日晚&#xff0c;随着Llama3.1的发布&#xff0c;Meta正式提出了“Llama系统”的概念&#xff0c;通过系统级的安全组件对…

谷粒商城实战笔记-62-商品服务-API-品牌管理-OSS整合测试

文章目录 一&#xff0c;Java中上传文件到阿里云OSS1&#xff0c;整合阿里云OSS2&#xff0c;测试上传文件 二&#xff0c;Java中整合阿里云OSS服务指南引言准备工作1. 注册阿里云账号2. 获取Access Key3. 添加依赖 实现OSS客户端1. 初始化OSSClient2. 创建Bucket3. 上传文件4.…

自定义 RAG 工作流:在 IDE 中结合 RAG 编排,构建可信的编码智能体

构建编码智能体并非一件容易的事。结合我们在 AutoDev、ArchGuard Co-mate、ChocoBuilder 等智能体项目的经验&#xff0c;我们开始思考在 Shire 语言中提供一种新的 RAG 工作流。结合我们先前构建的 IDE 基础设施&#xff08;代码生成、代码校验、代码执行等接口&#xff09;&…

基于PaddleClas的人物年龄分类项目

目录 一、任务概述 二、算法研发 2.1 下载数据集 2.2 数据集预处理 2.3 安装PaddleClas套件 2.4 算法训练 2.5 静态图导出 2.6 静态图推理 三、小结 一、任务概述 最近遇到个需求&#xff0c;需要将图像中的人物区分为成人和小孩&#xff0c;这是一个典型的二分类问题…