设计杂谈-工厂模式

“工厂”模式在各种框架中非常常见,包括 MyBatis,它是一种创建对象的设计模式。使用工厂模式有很多好处,尤其是在复杂的框架中,它可以带来更好的灵活性、可维护性和可配置性

让我们以 MyBatis 为例,来理解工厂模式及其优点:

MyBatis 中的工厂:SqlSessionFactoryBuilderSqlSessionFactory

在 MyBatis 中,主要的工厂类是 SqlSessionFactoryBuilderSqlSessionFactory

  1. SqlSessionFactoryBuilder(构建器):

    • 它的作用是读取 MyBatis 的配置文件(例如 mybatis-config.xml)或通过 Java 代码构建 Configuration 对象Configuration 对象包含了 MyBatis 的所有配置信息,例如数据源、事务管理器、映射器等。
    • SqlSessionFactoryBuilder 的生命周期很短。一旦 SqlSessionFactory 被创建出来,SqlSessionFactoryBuilder 通常就不再需要了。你可以把它想象成一个“临时的工厂的建造者”。
  2. SqlSessionFactory(会话工厂):

    • 它的作用是根据 Configuration 对象创建一个 SqlSession 对象SqlSession 是 MyBatis 中与数据库交互的核心接口,通过它你可以执行 SQL 语句、管理事务等。
    • SqlSessionFactory 的生命周期通常是整个应用的生命周期。它是一个“持久的工厂”,负责生产 SqlSession

使用工厂模式的好处(以 MyBatis 为例):

  1. 封装对象的创建过程:

    • 工厂模式将对象的创建逻辑封装在一个或多个工厂类中。在 MyBatis 的例子中,创建 SqlSession 的复杂过程被封装在 SqlSessionFactory 中。
    • 客户端代码(你的业务代码)不需要知道创建 SqlSession 的具体细节,只需要从 SqlSessionFactory 获取即可。这降低了客户端代码的复杂性。
  2. 解耦对象的创建和使用:

    • 工厂模式将对象的创建和使用分离。你的业务代码依赖的是 SqlSession 接口,而 SqlSession 的具体实现是由 SqlSessionFactory 负责创建的。
    • 这种解耦使得在需要更换 SqlSession 的实现或者修改其创建方式时,你的业务代码不需要做大的改动,只需要修改工厂的配置即可。
  3. 提高灵活性和可配置性:

    • 通过配置文件(mybatis-config.xml)或编程方式配置 SqlSessionFactoryBuilder,你可以灵活地指定 MyBatis 的各种行为,例如使用哪个数据源、事务管理器、是否开启缓存等等。
    • SqlSessionFactory 会根据这些配置创建出具有相应特性的 SqlSession。这使得框架具有很高的可配置性。
  4. 隐藏对象的创建细节:

    • 工厂可以隐藏对象创建的复杂性,例如对象的初始化参数、依赖关系等。客户端只需要简单地向工厂请求对象,而不需要关心这些内部细节。
    • 在 MyBatis 中,SqlSessionFactory 负责处理数据源的创建、连接池的管理等复杂细节,客户端只需要获取 SqlSession 来执行 SQL。
  5. 控制对象的生命周期:

    • 工厂可以控制所创建对象的生命周期。例如,SqlSessionFactory 可以管理数据源和连接池的生命周期,而 SqlSession 的生命周期通常是请求级别的。
  6. 易于扩展和维护:

    • 当需要引入新的实现或者修改对象的创建逻辑时,只需要修改工厂类或其配置,而不需要修改所有使用该对象的客户端代码。这提高了框架的可扩展性和可维护性。

为了更直观地理解工厂模式的优势,我将提供一个简单的场景,分别用不用工厂模式来实现,并对比它们的差异。

场景:创建不同类型的日志记录器

假设我们需要根据配置创建不同类型的日志记录器,目前有两种:控制台日志记录器 (ConsoleLogger) 和文件日志记录器 (FileLogger)。

1. 不使用工厂模式的实现

interface Logger {void log(String message);
}class ConsoleLogger implements Logger {@Overridepublic void log(String message) {System.out.println("[Console] " + message);}
}class FileLogger implements Logger {private String filePath;public FileLogger(String filePath) {this.filePath = filePath;// 初始化文件写入器等...System.out.println("FileLogger initialized with path: " + filePath);}@Overridepublic void log(String message) {// 将消息写入文件System.out.println("[File] " + message + " (written to " + filePath + ")");}
}public class LoggingServiceWithoutFactory {private String loggerType;private String fileLogPath;public LoggingServiceWithoutFactory(String loggerType, String fileLogPath) {this.loggerType = loggerType;this.fileLogPath = fileLogPath;}public Logger getLogger() {if ("console".equalsIgnoreCase(loggerType)) {return new ConsoleLogger();} else if ("file".equalsIgnoreCase(loggerType)) {return new FileLogger(fileLogPath);} else {throw new IllegalArgumentException("Unsupported logger type: " + loggerType);}}public void logMessage(String message) {Logger logger = getLogger();logger.log(message);}public static void main(String[] args) {LoggingServiceWithoutFactory consoleService = new LoggingServiceWithoutFactory("console", null);consoleService.logMessage("Log to console.");LoggingServiceWithoutFactory fileService = new LoggingServiceWithoutFactory("file", "app.log");fileService.logMessage("Log to file.");// 如果要添加新的日志记录器类型,需要修改 LoggingServiceWithoutFactory}
}

缺点(不使用工厂模式):

  • 紧耦合: LoggingServiceWithoutFactory 类直接依赖于 ConsoleLoggerFileLogger 的具体实现。如果添加新的日志记录器类型,你需要修改 getLogger() 方法。
  • 违反开闭原则: 对修改开放(需要修改 getLogger()),对扩展关闭(不容易在不修改现有代码的情况下添加新的日志记录器)。
  • 创建逻辑分散: 创建不同类型 Logger 的逻辑集中在一个 getLogger() 方法中,如果创建逻辑变得复杂,这个方法会变得难以维护。
  • 客户端需要知道具体的类名: LoggingServiceWithoutFactory 的构造函数需要知道 loggerType 字符串,这间接暴露了具体的实现类名。

2. 使用工厂模式的实现

interface Logger {void log(String message);
}class ConsoleLogger implements Logger {@Overridepublic void log(String message) {System.out.println("[Console] " + message);}
}class FileLogger implements Logger {private String filePath;public FileLogger(String filePath) {this.filePath = filePath;System.out.println("FileLogger initialized with path: " + filePath);}@Overridepublic void log(String message) {System.out.println("[File] " + message + " (written to " + filePath + ")");}
}// 日志记录器工厂接口
interface LoggerFactory {Logger createLogger();
}// 控制台日志记录器工厂
class ConsoleLoggerFactory implements LoggerFactory {@Overridepublic Logger createLogger() {return new ConsoleLogger();}
}// 文件日志记录器工厂
class FileLoggerFactory implements LoggerFactory {private String filePath;public FileLoggerFactory(String filePath) {this.filePath = filePath;}@Overridepublic Logger createLogger() {return new FileLogger(filePath);}
}public class LoggingServiceWithFactory {private LoggerFactory loggerFactory;public LoggingServiceWithFactory(LoggerFactory loggerFactory) {this.loggerFactory = loggerFactory;}public void logMessage(String message) {Logger logger = loggerFactory.createLogger();logger.log(message);}public static void main(String[] args) {LoggerFactory consoleFactory = new ConsoleLoggerFactory();LoggingServiceWithFactory consoleService = new LoggingServiceWithFactory(consoleFactory);consoleService.logMessage("Log to console.");LoggerFactory fileFactory = new FileLoggerFactory("app.log");LoggingServiceWithFactory fileService = new LoggingServiceWithFactory(fileFactory);fileService.logMessage("Log to file.");// 要添加新的日志记录器类型,只需要创建新的 Logger 和 LoggerFactory}
}

优点(使用工厂模式):

  • 解耦: LoggingServiceWithFactory 类依赖于 LoggerFactory 接口,而不是具体的 Logger 实现。具体的 Logger 对象的创建由相应的工厂负责。
  • 符合开闭原则: 要添加新的日志记录器类型,你只需要创建新的 Logger 类和对应的 LoggerFactory 类,而不需要修改 LoggingServiceWithFactory 的代码。
  • 职责分离: 对象创建的逻辑被委托给专门的工厂类,使得 LoggingServiceWithFactory 专注于日志记录的服务逻辑。
  • 隐藏实现细节: LoggingServiceWithFactory 的构造函数接收 LoggerFactory 接口,不需要知道具体的 Logger 实现类名。
  • 更灵活的对象创建: 工厂可以包含更复杂的对象创建逻辑,例如读取配置文件、依赖注入等。

对比总结:

特性不使用工厂模式使用工厂模式
耦合度高,直接依赖具体实现低,依赖抽象(接口)
开闭原则违反,添加新类型需要修改现有代码符合,添加新类型只需创建新类
创建逻辑集中在 getLogger() 方法中分散在不同的工厂类中
灵活性较低,不易于扩展和修改较高,易于扩展和修改
客户端依赖间接依赖具体实现类名依赖抽象工厂接口
维护性随着类型的增加,getLogger() 方法变得难以维护每个工厂类职责单一,更易于维护

咱们用最简单的大白话总结一下“工厂模式”是干啥的,以及为啥像 MyBatis 这样的框架爱用它:

想象一下你要买不同口味的冰淇淋:

不用工厂模式就像这样:

  • 你直接跑到冰柜前,自己翻箱倒柜地找你想要的口味(比如草莓味、巧克力味)。
  • 如果下次出了个新口味(比如抹茶味),你就得知道这个新口味的名字,然后自己去冰柜里找。
  • 如果冰淇淋的制作过程很复杂(比如要加很多配料、特殊冷冻),你买的时候也得稍微了解一下,不然可能买错。

用工厂模式就像这样:

  • 你不去冰柜里直接找,而是找到一个“冰淇淋工厂的售货员”(这就是“工厂”)。
  • 你只需要告诉售货员你想要什么口味(比如“草莓味”)。
  • 售货员知道去哪里、怎么给你拿出正确的冰淇淋。
  • 如果出了新口味,你只需要告诉售货员这个新口味的名字,售货员自然会去工厂里帮你拿。
  • 你不需要知道冰淇淋是怎么做的,售货员(工厂)帮你处理好了一切。

总结一下“工厂模式”:

  • 简单来说: 就是专门找一个“家伙”(工厂)来帮你创建你需要的“东西”(对象),而不是你自己去直接创建。
  • 好处就像上面的冰淇淋例子:
    • 更省事: 你不用自己操心“东西”是怎么被创建出来的,交给工厂就行。
    • 更灵活: 如果想换一种“东西”或者创建“东西”的方式变了,你只需要告诉工厂,不用改你自己的用法。
    • 更好管理: 创建“东西”的逻辑都放在工厂里,管理起来更方便,不会乱糟糟地散在各处。

为啥像 MyBatis 这样的框架爱用工厂模式?

MyBatis 需要创建很多跟数据库打交道的“东西”(比如 SqlSession,就是用来执行 SQL 的)。创建这些“东西”可能挺复杂的,需要配置很多信息(连接哪个数据库、用什么方式等等)。

用了“工厂模式”(SqlSessionFactory 就是个工厂),你的代码就不用去管这些复杂的创建过程了,只需要跟工厂说“给我一个能干活的 SqlSession”,工厂就会根据它的配置帮你弄好。

这样一来:

  • 你的代码更干净: 不用一堆创建 SqlSession 的复杂代码。
  • MyBatis 更灵活: 如果你想换个数据库或者改一下连接方式,只需要改一下 MyBatis 的配置(告诉工厂),你的代码基本不用动。

LoggingServiceWithoutFactory 的构造函数需要知道 loggerType 字符串,这间接暴露了具体的实现类名。 为什么呢

LoggingServiceWithoutFactory 的构造函数中:

public LoggingServiceWithoutFactory(String loggerType, String fileLogPath) {this.loggerType = loggerType;this.fileLogPath = fileLogPath;
}

以及在 getLogger() 方法中:

public Logger getLogger() {if ("console".equalsIgnoreCase(loggerType)) {return new ConsoleLogger();} else if ("file".equalsIgnoreCase(loggerType)) {return new FileLogger(fileLogPath);} else {throw new IllegalArgumentException("Unsupported logger type: " + loggerType);}
}

为什么说构造函数需要知道 loggerType 字符串间接暴露了具体的实现类名?

  1. 字符串 loggerType 的含义: 传递给构造函数的 loggerType 字符串(例如 "console""file")并不是一个抽象的概念,而是直接对应着你希望创建的具体日志记录器类的名称(或其简写)。

  2. getLogger() 方法的逻辑: getLogger() 方法内部的 ifelse if 语句会根据 loggerType 字符串的值来硬编码地创建具体的 Logger 实现类的实例 (new ConsoleLogger()new FileLogger(fileLogPath))。

  3. 客户端代码的依赖: 当客户端代码创建 LoggingServiceWithoutFactory 的实例时,它必须知道要使用哪个 loggerType 字符串,而这个字符串的选择直接决定了最终会创建哪个具体的 Logger 实现类的对象。

    例如,在 main 方法中:

LoggingServiceWithoutFactory consoleService = new LoggingServiceWithoutFactory("console", null); // 客户端需要知道 "console" 对应 ConsoleLogger
LoggingServiceWithoutFactory fileService = new LoggingServiceWithoutFactory("file", "app.log");   // 客户端需要知道 "file" 对应 FileLogger
  • 这里,客户端代码需要使用字符串 "console" 来请求一个控制台日志记录器,使用字符串 "file" 来请求一个文件日志记录器。这些字符串与具体的类名 ConsoleLoggerFileLogger 之间存在着直接的、虽然是通过字符串间接的关联。

  • 修改的影响: 如果你想要添加一个新的日志记录器类型(比如 DatabaseLogger),你需要修改 LoggingServiceWithoutFactorygetLogger() 方法,增加一个新的 else if 分支来创建 DatabaseLogger 的实例。同时,客户端代码也需要知道使用一个新的字符串(比如 "database")来请求这个新的日志记录器。

对比使用工厂模式的情况:

在使用工厂模式的例子中,LoggingServiceWithFactory 的构造函数接收的是 LoggerFactory 接口:

public LoggingServiceWithFactory(LoggerFactory loggerFactory) {this.loggerFactory = loggerFactory;
}

客户端代码直接传递一个实现了 LoggerFactory 接口的具体工厂对象(例如 ConsoleLoggerFactoryFileLoggerFactory):

LoggerFactory consoleFactory = new ConsoleLoggerFactory();
LoggingServiceWithFactory consoleService = new LoggingServiceWithFactory(consoleFactory);LoggerFactory fileFactory = new FileLoggerFactory("app.log");
LoggingServiceWithFactory fileService = new LoggingServiceWithFactory(fileFactory);

在这里,LoggingServiceWithFactory 不直接依赖于具体的 Logger 实现类名,而是依赖于一个抽象的工厂接口。客户端代码虽然仍然需要知道具体的工厂类名,但 LoggingServiceWithFactory 本身与具体的 Logger 实现类解耦了。

总结:

在不使用工厂模式的例子中,loggerType 字符串充当了一个“配置标识符”,客户端代码通过这个标识符间接地告诉 LoggingServiceWithoutFactory 需要创建哪个具体的 Logger 实现类的对象。虽然没有直接使用类名,但字符串的值与具体的类名之间存在着明确的映射关系,这仍然是一种形式的依赖,使得添加新的日志记录器类型需要修改 LoggingServiceWithoutFactory 类的代码。这就是为什么说构造函数需要知道 loggerType 字符串间接暴露了具体的实现类名。

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

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

相关文章

AI与IoT携手,精准农业未来已来

AIoT:农业领域的变革先锋 在科技飞速发展的当下,人工智能(AI)与物联网(IoT)的融合 ——AIoT,正逐渐成为推动各行业变革的关键力量,农业领域也不例外。AIoT 技术通过将 AI 的智能分析…

排错-harbor-db容器异常重启

排错-harbor-db容器异常重启 环境: docker 19.03 , harbor-db(postgresql) goharbor/harbor-db:v2.5.6 现象: harbor-db 容器一直restart,查看日志发现 报错 initdb: error: directory "/var/lib/postgresql/data/pg13" exists…

Docker容器启动失败?无法启动?

Docker容器无法启动的疑难杂症解析与解决方案 一、问题现象 Docker容器无法启动是开发者在容器化部署中最常见的故障之一。尽管Docker提供了丰富的调试工具,但问题的根源往往隐藏在复杂的配置、环境依赖或资源限制中。本文将从环境变量配置错误这一细节问题入手&am…

查看购物车

一.查看购物车 查看购物车使用get请求。我们要查看当前用户的购物车,就要获取当前用户的userId字段进行条件查询。因为在用户登录时就已经将userId封装在token中了,因此我们只需要解析token获取userId即可,不需要前端再传入参数了。 Control…

C/C++ 内存管理深度解析:从内存分布到实践应用(malloc和new,free和delete的对比与使用,定位 new )

一、引言:理解内存管理的核心价值 在系统级编程领域,内存管理是决定程序性能、稳定性和安全性的关键因素。C/C 作为底层开发的主流语言,赋予开发者直接操作内存的能力,却也要求开发者深入理解内存布局与生命周期管理。本文将从内…

使用Stable Diffusion(SD)中CFG参数指的是什么?该怎么用!

1.定义: CFG参数控制模型在生成图像时,对提示词(Prompt)的“服从程度”。 它衡量模型在“完全根据提示词生成图像”和“自由生成图像”(不参考提示词)之间的权衡程度。 数值范围:常见范围是 1 …

【GESP】C++三级练习 luogu-B2156 最长单词 2

GESP三级练习,字符串练习(C三级大纲中6号知识点,字符串),难度★★☆☆☆。 题目题解详见:https://www.coderli.com/gesp-3-luogu-b2156/ 【GESP】C三级练习 luogu-B2156 最长单词 2 | OneCoderGESP三级练…

Linux网络基础 -- 局域网,广域网,网络协议,网络传输的基本流程,端口号,网络字节序

目录 1. 计算机网络背景 1.1 局域网 1.1.2 局域网的组成 1.2 广域网 1.1.2 广域网的组成 2. 初始网络协议 2.1 网络协议的定义和作用 2.2 网络协议的分层结构 2.2.1 OSI七层模型 2.2.2 TCP/IP 五层(四层)模型 3. 再识网络协议 3.1 为什么要有…

【PostgreSQL】超简单的主从节点部署

1. 启动数据库 启动主节点 docker run --name postgres-master -e POSTGRES_PASSWORDmysecretpassword -p 5432:5432 -d postgres启动从节点 docker run --name postgres-slave -e POSTGRES_PASSWORDmysecretpassword -p 5432:5432 -d postgres需要配置挂载的存储卷 2. 数据…

c#修改ComboBox当前选中项的文本

对于一个C#的Combobox列表,类型设置为下拉样式,不允许输入,只能选择,样子如下: 该控件的名字为 cbb1,如果要修改当前这个“A1区”的文件,则用如下方式: cbb1.Items[cbb1.SelectedInd…

Java设计模式之适配器模式:从入门到精通

适配器模式(Adapter Pattern)是Java中最常用的结构型设计模式之一,它像一座桥梁连接两个不兼容的接口,使得原本由于接口不兼容而不能一起工作的类可以协同工作。本文将全面深入地解析适配器模式,从基础概念到高级应用,包含丰富的代码示例、详细注释、使用场景分析以及多维对…

中国黄土高原中部XF剖面磁化率和粒度数据

时间分辨率&#xff1a;1000年 < x空间分辨率为&#xff1a;空共享方式&#xff1a;申请获取数据大小&#xff1b;35.75 KB数据时间范围&#xff1a;743-0 ka元数据更新时间&#xff1a;2023-08-15 数据集摘要 该数据集包括中国黄土高原中部XF剖面磁化率和粒度数据。将所有…

【Python训练营打卡】day23 @浙大疏锦行

test pipeline管道 知识回顾: 1. 转化器和估计器的概念 2. 管道工程 3. ColumnTransformer和Pipeline类 作业&#xff1a; 整理下全部逻辑的先后顺序&#xff0c;看看能不能制作出适合所有机器学习的通用pipeline 伪代码 # 适合所有机器学习的通用pipeline #伪代码 import p…

【android bluetooth 框架分析 02】【Module详解 13】【CounterMetrics 模块介绍】

1. CounterMetrics 介绍 CounterMetrics 模块代码很少&#xff0c; 我简单介绍一下。 // system/gd/metrics/counter_metrics.cc #define LOG_TAG "BluetoothCounterMetrics"#include "metrics/counter_metrics.h"#include "common/bind.h" #i…

QMK键盘固件配置详解

QMK键盘固件配置详解 前言 大家好&#xff01;今天给大家带来QMK键盘固件配置的详细指南。如果你正在DIY机械键盘或者想要给自己的键盘刷固件&#xff0c;这篇文章绝对不容错过。QMK是目前最流行的开源键盘固件框架之一&#xff0c;它允许我们对键盘进行高度自定义。接下来&a…

基于STM32、HAL库的DPS368XTSA1气压传感器 驱动程序设计

一、简介: DPS368XTSA1 是 InvenSense(TDK 集团旗下公司)生产的一款高精度数字气压传感器,专为需要精确测量气压和温度的应用场景设计。它具有超低功耗、高精度、快速响应等特点,非常适合物联网、可穿戴设备和无人机等应用。 二、硬件接口: DPS368XTSA1 引脚STM32L4XX 引…

因子分析——数学原理及R语言代码

正交因子分析 目的数学原理参数估计方法主成分法主因子法极大似然法 因子旋转模型检验因子得分加权最小二乘法回归法 代码实现注意事项例子 Reference 目的 FactorAnalysis的目的是从多个高度相关的观测变量中提取出少数几个LatentFactor&#xff0c;这些因子代表了变量背后的…

ACL访问控制列表:access-list 10 permit 192.168.10.1

ACL访问控制列表 标准ACL语法 1. 创建ACL access-list <编号> <动作> <源IP> <通配符掩码> // 编号范围 1-99 // 动作&#xff1a;permit 允许 、 deny 拒绝2. 示例 //允许192.168.1.0/24g整个网络,0.0.0.255 反掩码 access-list 10 permit 192.1…

解决社区录音应用横屏状态下,录音后无法播放的bug

最近看到社区有小伙伴反映&#xff0c;社区录音应用横屏时&#xff0c;录音后无法播放的问题。现分享解决办法。 社区录音应用的来源&#xff1a;https://gitee.com/openharmony/applications_app_samples/tree/OpenHarmony-5.0.2-Release/code/SystemFeature/Media/Recorder …

每周靶点分享:Angptl3、IgE、ADAM9及文献分享:抗体的多样性和特异性以及结构的新见解

本期精选了《脂质代谢的关键调控者Angptl3》《T细胞活化抑制因子VISTA靶点》《文献分享&#xff1a;双特异性抗体重轻链配对设计》三篇文章。以下为各研究内容的概述&#xff1a; 1. 脂质代谢的关键调控者Angptl3 血管生成素相关蛋白3&#xff08;Angptl3&#xff09;是血管生…