【设计模式】【创建型模式】工厂方法模式(Factory Methods)

👋hi,我不是一名外包公司的员工,也不会偷吃茶水间的零食,我的梦想是能写高端CRUD
🔥 2025本人正在沉淀中… 博客更新速度++
👍 欢迎点赞、收藏、关注,跟上我的更新节奏
🎵 当你的天空突然下了大雨,那是我在为你炸乌云

文章目录

  • 一、入门
    • 什么是工厂方法模式?
    • 为什么要工厂方法模式?
    • 如何实现工厂方法模式
  • 二、工厂方法模式在框架源码中的运用
    • Java 集合框架中的工厂方法模式
    • Spring 框架中的工厂方法模式
  • 三、总结
    • 工厂方法模式的优点
    • 工厂方法模式的缺点
    • 工厂方法模式的适用场景

一、入门

什么是工厂方法模式?

工厂方法模式(Factory Method Pattern)是一种创建型设计模式,它定义了一个用于创建对象的接口,但由子类决定实例化哪个类。工厂方法模式使类的实例化延迟到子类。
简单工厂方法就是所有的产品都交给一个工厂来处理,而工厂方法是交给多个工厂。

为什么要工厂方法模式?

假设我们正在开发一个支付系统,支持多种支付方式(如支付宝、微信支付、银行卡支付等)。在没有工厂方法模式的情况下,代码可能是这样的:

// 支付接口
interface Payment {void pay(double amount);
}// 支付宝支付
class Alipay implements Payment {@Overridepublic void pay(double amount) {System.out.println("使用支付宝支付: " + amount + " 元");}
}// 微信支付
class WechatPay implements Payment {@Overridepublic void pay(double amount) {System.out.println("使用微信支付: " + amount + " 元");}
}// 银行卡支付
class BankCardPay implements Payment {@Overridepublic void pay(double amount) {System.out.println("使用银行卡支付: " + amount + " 元");}
}// 客户端代码
public class PaymentSystem {public static void main(String[] args) {String paymentType = "alipay"; // 假设从配置或用户输入中获取支付方式Payment payment = null;if (paymentType.equals("alipay")) {payment = new Alipay();} else if (paymentType.equals("wechatpay")) {payment = new WechatPay();} else if (paymentType.equals("bankcard")) {payment = new BankCardPay();} else {throw new IllegalArgumentException("不支持的支付方式");}payment.pay(100.0); // 执行支付}
}

问题分析

  1. 代码耦合性高
    • 客户端代码(PaymentSystem)直接依赖具体的支付实现类(如 Alipay、WechatPay 等)。
    • 如果需要新增一种支付方式(如 ApplePay),必须修改客户端代码,增加新的 if-else 分支。
  2. 违反开闭原则
    • 开闭原则(Open/Closed Principle)要求软件实体应对扩展开放,对修改关闭。
    • 在当前实现中,每次新增支付方式都需要修改客户端代码,而不是通过扩展来实现。
  3. 代码重复
    • 如果在多个地方需要创建支付对象,相同的 if-else 逻辑会重复出现,导致代码冗余。
  4. 难以测试
    • 客户端代码与具体支付实现类耦合,难以进行单元测试(例如,无法轻松模拟支付对象)。

如何实现工厂方法模式

主要角色

  1. 抽象产品(Product):定义产品的接口。
  2. 具体产品(Concrete Product):实现抽象产品接口的具体类。
  3. 抽象工厂(Creator):声明工厂方法,返回一个产品对象。
  4. 具体工厂(Concrete Creator):实现工厂方法,返回具体产品的实例。

【案例】支付方式 - 改
在这里插入图片描述
抽象产品(Product):支付方式,Payment类。

// 支付接口
interface Payment {void pay(double amount);
}

具体产品(Concrete Product): 支付宝支付(Alipay类)、微信支付(WechatPay类)、银行卡支付(BankCardPay类)。

// 支付宝支付
class Alipay implements Payment {@Overridepublic void pay(double amount) {System.out.println("使用支付宝支付: " + amount + " 元");}
}// 微信支付
class WechatPay implements Payment {@Overridepublic void pay(double amount) {System.out.println("使用微信支付: " + amount + " 元");}
}// 银行卡支付
class BankCardPay implements Payment {@Overridepublic void pay(double amount) {System.out.println("使用银行卡支付: " + amount + " 元");}
}

抽象工厂(Creator): 支付工厂,PaymentFactory类。

interface PaymentFactory {Payment createPayment();
}

具体工厂(Concrete Creator)AlipayFactoryWechatPayFactoryBankCardPayFactory

// 支付宝支付工厂
class AlipayFactory implements PaymentFactory {@Overridepublic Payment createPayment() {return new Alipay();}
}// 微信支付工厂
class WechatPayFactory implements PaymentFactory {@Overridepublic Payment createPayment() {return new WechatPay();}
}// 银行卡支付工厂
class BankCardPayFactory implements PaymentFactory {@Overridepublic Payment createPayment() {return new BankCardPay();}
}

客户端调用

public class PaymentSystem {// 支付方式与工厂类的映射private static final Map<String, PaymentFactory> paymentFactories = new HashMap<>();static {// 初始化映射关系paymentFactories.put("alipay", new AlipayFactory());paymentFactories.put("wechatpay", new WechatPayFactory());paymentFactories.put("bankcard", new BankCardPayFactory());}// 获取支付对象public static Payment getPayment(String paymentType) {PaymentFactory factory = paymentFactories.get(paymentType);if (factory == null) {throw new IllegalArgumentException("不支持的支付方式: " + paymentType);}return factory.createPayment();}public static void main(String[] args) {String paymentType = "alipay"; // 假设从配置或用户输入中获取支付方式// 获取支付对象并执行支付Payment payment = getPayment(paymentType);payment.pay(100.0);}
}

二、工厂方法模式在框架源码中的运用

Java 集合框架中的工厂方法模式

Java 集合框架中的 Collections 工具类提供了多个静态工厂方法,用于创建不可变集合、同步集合等。
示例:Collections.unmodifiableList()
Collections.unmodifiableList() 是一个工厂方法,用于创建不可修改的列表。

public class Collections {public static <T> List<T> unmodifiableList(List<? extends T> list) {return new UnmodifiableList<>(list);}private static class UnmodifiableList<E> extends UnmodifiableCollection<E>implements List<E> {// 具体实现...}
}
  • 工厂方法:unmodifiableList() 是工厂方法,负责创建 UnmodifiableList 对象。
  • 客户端:客户端通过调用 unmodifiableList() 方法获取不可修改的列表,而无需关心具体的实现类。
List<String> originalList = new ArrayList<>();
originalList.add("A");
originalList.add("B");List<String> unmodifiableList = Collections.unmodifiableList(originalList);
// unmodifiableList.add("C"); // 抛出 UnsupportedOperationException
  • 解耦:客户端不需要知道 UnmodifiableList 的具体实现,只需要通过工厂方法获取对象。
  • 安全性:工厂方法返回的对象是不可修改的,保证了数据的安全性。

Spring 框架中的工厂方法模式

Spring 框架广泛使用工厂方法模式来创建和管理 Bean 对象。Spring 的 BeanFactoryApplicationContext 是典型的工厂方法模式的实现。
示例:BeanFactory
BeanFactory 是 Spring 的核心接口,负责创建和管理 Bean 对象。它定义了工厂方法 getBean(),用于获取 Bean 实例。

public interface BeanFactory {Object getBean(String name) throws BeansException;<T> T getBean(String name, Class<T> requiredType) throws BeansException;// 其他方法...
}
  • 工厂方法getBean() 是工厂方法,负责根据名称或类型创建并返回 Bean 对象。
  • 具体工厂:Spring 提供了多个 BeanFactory 的实现类,例如 DefaultListableBeanFactory
  • 客户端:Spring 容器(如 ApplicationContext)通过调用 getBean() 方法来获取 Bean 对象。
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
MyService myService = context.getBean("myService", MyService.class);
myService.doSomething();
  • 解耦:客户端代码(如 MyService)不需要知道具体的 Bean 创建逻辑,只需要通过 getBean() 方法获取对象。
  • 灵活性:Spring 可以通过配置文件或注解动态决定创建哪个 Bean 对象。

三、总结

工厂方法模式的优点

  1. 解耦
    • 工厂方法模式将对象的创建与使用分离,客户端代码只需要依赖抽象的工厂接口和产品接口,而不需要关心具体的实现类。
    • 例如,在 Spring 框架中,客户端通过 BeanFactory 获取 Bean 对象,而不需要知道具体的 Bean 实现类。
  2. 符合开闭原则
    • 当需要新增一种产品时,只需新增一个具体工厂类和具体产品类,而无需修改现有代码。
    • 例如,在支付系统中,新增一种支付方式(如 ApplePay)时,只需新增 ApplePayFactoryApplePay 类,而不需要修改客户端代码。
  3. 可扩展性
    • 工厂方法模式支持动态扩展,可以通过配置文件、依赖注入等方式动态决定使用哪个具体工厂类。
    • 例如,在 JDBC 中,可以通过配置文件动态切换数据库驱动。
  4. 代码复用
    • 工厂类的逻辑可以复用在多个地方,避免了重复的创建逻辑。
    • 例如,在 Log4j 中,Logger.getLogger() 方法可以在多个地方复用,而不需要重复编写日志记录器的创建逻辑。
  5. 易于测试
    • 客户端代码依赖于抽象的工厂接口和产品接口,可以通过模拟工厂类来测试客户端代码,而不需要依赖具体的实现类。

工厂方法模式的缺点

  1. 类的数量增加
    • 每新增一种产品,就需要新增一个具体工厂类和具体产品类,导致类的数量增加,系统复杂度提高。
    • 例如,在支付系统中,如果有 10 种支付方式,就需要 10 个具体工厂类和 10 个具体产品类。
  2. 增加了系统抽象性
    • 工厂方法模式引入了抽象的工厂接口和产品接口,增加了系统的抽象性,可能会使代码更难理解。
    • 例如,对于初学者来说,理解 Spring 的 BeanFactoryApplicationContext 可能需要一定的学习成本。
  3. 不适合简单场景
    • 如果对象的创建逻辑非常简单,使用工厂方法模式可能会显得过于复杂,增加不必要的代码量。
    • 例如,如果只需要创建一个简单的对象,直接使用 new 关键字可能更合适。

工厂方法模式的适用场景

  1. 需要动态创建对象
    • 当对象的创建逻辑需要根据运行时条件动态决定时,工厂方法模式非常适用。
    • 例如,在支付系统中,根据用户选择的支付方式动态创建支付对象。
  2. 需要解耦对象的创建与使用
    • 当客户端代码不需要关心具体对象的创建逻辑时,可以使用工厂方法模式将对象的创建过程封装起来。
    • 例如,在 Spring 框架中,客户端通过 BeanFactory 获取 Bean 对象,而不需要知道具体的 Bean 实现类。
  3. 需要支持多种产品类型
    • 当系统需要支持多种产品类型,并且这些产品有共同的接口时,可以使用工厂方法模式。
    • 例如,在日志框架中,支持多种日志实现(如文件日志、控制台日志等)。
  4. 需要符合开闭原则
    • 当系统需要频繁扩展新的产品类型,并且希望在不修改现有代码的情况下实现扩展时,可以使用工厂方法模式。
    • 例如,在 GUI 库中,支持新增一种控件类型(如按钮、文本框等)。
  5. 需要集中管理对象的创建逻辑
    • 当对象的创建逻辑比较复杂,或者需要在多个地方复用时,可以使用工厂方法模式将创建逻辑集中管理。
    • 例如,在 JDBC 中,通过 DriverManager.getConnection() 集中管理数据库连接的创建逻辑。

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

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

相关文章

DeepSeek模型快速部署教程-搭建自己的DeepSeek

前言&#xff1a;在人工智能技术飞速发展的今天&#xff0c;深度学习模型已成为推动各行各业智能化转型的核心驱动力。DeepSeek 作为一款领先的 AI 模型&#xff0c;凭借其高效的性能和灵活的部署方式&#xff0c;受到了广泛关注。无论是自然语言处理、图像识别&#xff0c;还是…

Deepseek 与 ChatGPT:AI 浪潮中的双子星较量

引言 在人工智能飞速发展的当下&#xff0c;AI 语言模型成为了人们关注的焦点。Deepseek 与 ChatGPT 作为其中的佼佼者&#xff0c;各自展现出独特的魅力&#xff0c;引领着 AI 技术的发展潮流。今天&#xff0c;就让我们深入探讨这两款模型&#xff0c;看看它们在 AI 领域中是…

QT事件循环

文章目录 主事件循环事件循环事件调度器事件处理投递事件发送事件 事件循环的嵌套线程的事件循环deleteLater与事件循环QEventLoop类QEventLoop应用等待一段时间同步操作模拟模态对话框 参考 本文主要对QT中的事件循环做简单介绍和使用 Qt作为一个跨平台的UI框架&#xff0c;其…

解决DeepSeek服务器繁忙问题的实用指南

目录 简述 1. 关于服务器繁忙 1.1 服务器负载与资源限制 1.2 会话管理与连接机制 1.3 客户端配置与网络问题 2. 关于DeepSeek服务的备用选项 2.1 纳米AI搜索 2.2 硅基流动 2.3 秘塔AI搜索 2.4 字节跳动火山引擎 2.5 百度云千帆 2.6 英伟达NIM 2.7 Groq 2.8 Firew…

进程等待和进程程序替换

进程控制 进程等待进程程序替换 进程等待 如果子进程没有退出 而父进程在进行执行waitpid进行等待&#xff0c;阻塞等待&#xff0c; 进程阻塞了 在等待某种条件发生&#xff08;子进程退出&#xff09; 进程程序替换 1 #include <stdio.h>2 #include <unistd.h>3…

UEFI Spec 学习笔记---6 - Block Translation Table (BTT) Layout

6.1 Block Translation Table (BTT) Background 定义个一个连续地址的非易失性的namespace&#xff0c;就是将一整个namespace 拆分成一个个block&#xff0c;其中的地址保存至BBT&#xff08;块转换表&#xff09;&#xff0c;这样可以防止扇区撕裂&#xff08;由于电源问题导…

SAP 代码扫描工具

描述&#xff1a; ZSCANNER是一个先进的代码分析工具&#xff0c;旨在提供对程序和功能模块内部工作的全面见解。它揭示了代码的技术细节&#xff0c;包括正在创建、读取、更新或删除的数据表&#xff08;CRUD操作&#xff09;&#xff0c;以及正在调用的类、功能模块和BAPI&a…

c语言基础第12节《函数的调用》

c语言基础10 函数 函数的调用 调用方式 ①函数语句&#xff1a; test(); // 对于无返回值的函数&#xff0c;直接调用 int res max(2,4); // 对于有返回值的函数&#xff0c;一般需要再主调函数中接收被调函数的返回值。②函数表达式&#xff1a; 4 max(2,4) scanf(&qu…

C++:iterator迭代器失效

说明&#xff1a;这里拿 vector 举例。 原因一&#xff1a;扩容导致迭代器失效 // 迭代器失效 void insert(iterator pos, const T& x) {assert(pos > _start);assert(pos < _finish);// 满了就扩容if (_finish _end_of_storage){reserve(capacity() 0 ? 4 : ca…

QT之改变鼠标样式

QT改变鼠标图片 资源路径如下 代码实现 QPixmap customCursorPixmap(":/images/mouse.png");QCursor customCursor(customCursorPixmap);QWidget::setCursor(customCursor); // 可以设置为整个窗口或特定控件QWidget::setCursor(); // 设置为透明光标&#xff0c…

用DeepSeek零基础预测《哪吒之魔童闹海》票房——从数据爬取到模型实战

系列文章目录 1.元件基础 2.电路设计 3.PCB设计 4.元件焊接 5.板子调试 6.程序设计 7.算法学习 8.编写exe 9.检测标准 10.项目举例 11.职业规划 文章目录 **一、为什么要预测票房&#xff1f;****二、准备工作****三、实战步骤详解****Step 1&#xff1a;数据爬取与清洗&am…

高并发下秒杀系统的设计

文章目录 1 业界通用做法1.1 压力分摊1.2 RedisMySQL1.3 Inventory Hint1.4 压力分摊RedisMQ 2 Redis MQ 解决高并发下的秒杀场景2.1 Redis库存预扣减2.1.1 lua脚本执行流程&#xff1a;2.1.2 Lua脚本主要做了几件事&#xff1a; 2.2 MySQL库存扣减2.3 记录操作流水的原因 3 I…

双重差分学习笔记

双重差分适用的研究场景&#xff1a; 研究某项政策或者冲击造成的影响 例如&#xff0c;某某小学在2024.12.12日颁布了小红花激励措施&#xff0c;我们要研究这项措施对学生成绩的影响&#xff0c;此时&#xff0c;就可以使用双重差分模型。 双重差分适用的数据类型&#xf…

深入理解 C++17 中的 std::atomic<T>::is_always_lock_free

文章目录 原子操作与锁无关性&#xff08;Lock-Free&#xff09;锁无关性&#xff08;Lock-Free&#xff09;无锁&#xff08;Lock-Free&#xff09;与无阻塞&#xff08;Wait-Free&#xff09; std::atomic<T>::is_always_lock_free 是什么&#xff1f;truefalse与 is_l…

VSCode 中 Git 添加了多个远端,如何设置默认远端

VSCode 中 Git 添加了多个远端&#xff0c;如何设置默认远端 查看分支&#xff1a;设置默认远端手动指定远端 查看分支&#xff1a; * 表示当前默认远端 git branch -vv* master a1b2c3d [origin/main] Fix typo dev d4e5f6g [upstream/dev] Add feature设置默认远端 将本…

一文讲清 AIO BIO NIO的区别

引言 在 Java 编程中&#xff0c;BIO&#xff08;Blocking I/O&#xff09;、NIO&#xff08;Non-blocking I/O&#xff09;和 AIO&#xff08;Asynchronous I/O&#xff09;是三种不同的 I/O 模型&#xff0c;它们在处理输入输出操作时有着不同的机制和特点&#xff0c;但是市…

使用(xshell+xftp)将前端项目部署到服务器

一.以vue项目为例 将项目打包生成dist文件 二.下载载安装xshell和xftp 下载地址&#xff1a;家庭/学校免费 - NetSarang Website 三.连接服务器 在xshell新建会话&#xff08;需要用到服务器、用户名、密码、端口号&#xff09;正确输入后连接到服务器 使用命令连接&#x…

硬件岗位是否适合你?

在当今科技飞速发展的时代,硬件行业作为技术创新的基石,始终扮演着至关重要的角色。无论是智能手机、自动驾驶汽车,还是人工智能服务器,硬件都是这些技术的核心支撑。然而,硬件岗位是否适合你?作为一名硬件专家,我将从多个角度为你分析,帮助你判断自己是否适合从事硬件…

Linux基本指令(二)

文章目录 基本指令echocat&#xff08;输入重定向&#xff09;history日志moretail和headmv&#xff08;重要&#xff09;时间相关的指令查找的命令 知识点Linux下一切皆文件为什么计算机关机了&#xff0c;开机后还能准确地记录时间呢&#xff1f; 基本指令 echo 1. echo&…

【Blender】二、建模篇--05,阵列修改器与晶格形变

阵列修改器是bender里面一个比较常用的修改器,所以我们单独开口来讲,我们会先从几片树叶出发,然后我们用阵列修改器把这几片树叶变成这样的造型和这样的造型。这两个造型分别就代表着阵列修改器最常用的两种偏移方法,我们现在就开始我们先来做几个树叶。 1.树叶建模 首先…