设计杂谈-工厂模式

news2025/5/23 16:38:36

“工厂”模式在各种框架中非常常见,包括 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 {
    @Override
    public 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);
    }

    @Override
    public 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 {
    @Override
    public 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);
    }

    @Override
    public void log(String message) {
        System.out.println("[File] " + message + " (written to " + filePath + ")");
    }
}

// 日志记录器工厂接口
interface LoggerFactory {
    Logger createLogger();
}

// 控制台日志记录器工厂
class ConsoleLoggerFactory implements LoggerFactory {
    @Override
    public Logger createLogger() {
        return new ConsoleLogger();
    }
}

// 文件日志记录器工厂
class FileLoggerFactory implements LoggerFactory {
    private String filePath;

    public FileLoggerFactory(String filePath) {
        this.filePath = filePath;
    }

    @Override
    public 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.coloradmin.cn/o/2384017.html

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

相关文章

SC3000智能相机-自动存图

1、需求:SC3000智能相机开机自动存图。相机自带的相机存储空间有限,预留存图需要开启SCMVS、并手动点存图。如果工人忘了开启则不会存图,导致生产严重失误! 2、方法:利用相机提供的FTP协议,将图自动存到本地。 1、在本地建立FTP服务器。 (1)win10默认开启了FTP服务器…

【Java高阶面经:微服务篇】5.限流实战:高并发系统流量治理全攻略

一、限流阈值的三维度计算模型 1.1 系统容量基准线:压测驱动的安全水位 1.1.1 压力测试方法论 测试目标:确定系统在资源安全水位(CPU≤80%,内存≤70%,RT≤500ms)下的最大处理能力测试工具: 单机压测:JMeter(模拟10万并发)、wrk(低资源消耗)集群压测:LoadRunner …

2025中青杯数学建模B题思路+模型+代码

本文将为大家带来2025年中青杯的选题建议,旨在十分钟内帮助大家快速了解每个题目具体难点、涉及模型等。初步预估赛题难度 A:B:C4:5:3初步预测选题人数 A:B:C2:1:0.6 首先是C题,忧郁症的双重防线:精准预测与有效治疗,这个题目涉及…

记录:uniapp 上线部署到微信小程序vendorjs包过大的问题

问题: 在代码依赖分析图中,可以看到主包的容量已经超过了2M了,分包没有超! 根据网上的资料的解决方案,当前我已经做了以下相关的配置: 1.分包 2.在manifest.json的(mp-weixin)节点…

asp.net core 添加 EntityFrame

1:Nuget 引入程序集 Microsoft.EntityFrameworkCore Microsoft.EntityFrameworkCore.SqlServer Microsoft.EntityFrameworkCore.SqlServer.Design Microsoft.EntityFrameworkCore.Tools 2:执行脚本 Scaffold-DbContext "Data Source.;Initial Ca…

微软全新开源的Agentic Web网络项目:NLWeb,到底是什么 ?

目录 1、背景 2、NLWeb是什么? 3、NLWeb是如何工作的? 3.1 技术原理 3.2 对发布者的价值 3.3 核心团队与合作伙伴 4、快速入门指南 5、延伸阅读 Agentic:Agent的形容词,Agentic指系统由大型语言模型(LLM&#…

Idea出现 100% classes 等

总是误点出来,每次又忘了怎么消除,在这里记录一下。 出现这样: 操作idea界面的:点击View->Tool Windows ->Coverage,然后关掉

【学习笔记】计算机操作系统(五)—— 虚拟存储器

第五章 虚拟存储器 文章目录 第五章 虚拟存储器5.1 虚拟存储器概述5.1.1 常规存储管理方式的特征和局部性原理5.1.2 虚拟存储器的定义和特征5.1.3 虚拟存储器的实现方法 5.2 请求分页存储管理方式5.2.1 请求分页中的硬件支持5.2.2 请求分页中的内存分配5.2.3 页面调入策略 5.3 …

乘最多水的容器 | 算法 | 给定一个整数数组。有n条垂线。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

在我们日常生活中,蓄水似乎是一个极为朴素的物理行为:两堵墙之间,注入水,看谁能装得更多。可如果换个角度,从算法的视角去看这个问题,它会变得怎样?你是否意识到,这样一个简单的问题…

【Java高阶面经:数据库篇】19、分库分表查询困境:无分库分表键时的高效应对

一、分库分表下的无分片键查询困境 在分布式数据库架构中,分库分表通过分片键(如买家ID)将数据分散存储,显著提升了单表性能和系统扩展性。然而,当业务需要从非分片键维度(如卖家ID)进行查询时,传统架构暴露出以下核心问题: 1.1 跨分片扫描的性能灾难 数据分散性:以…

spring中的BeanFactoryAware接口详解

一、接口定义与核心作用 BeanFactoryAware 是 Spring 框架提供的一个回调接口,允许 Bean 在初始化阶段获取其所属的 BeanFactory 实例。该接口定义如下: public interface BeanFactoryAware {void setBeanFactory(BeanFactory beanFactory) throws Bea…

Unity Hub打不开项目一直在加载

Unity Hub打不开项目,一直在加载。 运行环境:win10 解决方法:退还个人许可证,退出UnityHub重新登录后,再次获取个人许可证 Tips: 国内连续超过三天不登陆就需要激活一次。(每天登陆一次会自动续时间吗&…

蓝桥杯19681 01背包

问题描述 有 N 件物品和一个体积为 M 的背包。第 i 个物品的体积为 vi​,价值为 wi​。每件物品只能使用一次。 请问可以通过什么样的方式选择物品,使得物品总体积不超过 M 的情况下总价值最大,输出这个最大价值即可。 输入格式 第一行输…

ElasticSearch导读

ElasticSearch 简介:ElasticSearch简称ES是一个开源的分布式搜素和数据分析引擎。是使用Java开发并且是当前最流行的开源的企业级搜索引擎,能够达到近实时搜索,它专门设计用于处理大规模的文本数据和实现高性能的全文搜索。它基于 Apache Luc…

【机器学习】 关于外插修正随机梯度方法的数值实验

1. 随机梯度下降(SGD) 迭代格式: x k 1 x k − η k ∇ f i ( x k ) x_{k1} x_k - \eta_k \nabla f_i(x_k) xk1​xk​−ηk​∇fi​(xk​) 其中, η k \eta_k ηk​ 为步长(可能递减), ∇ f…

结构型:组合模式

目录 1、核心思想 2、实现方式 2.1 模式结构 2.2 实现案例 3、优缺点分析 4、适用场景 1、核心思想 目的:将总是在重复、迭代地显示的某种自相似性的结构(部分与整体结构特征相似),例如树形结构,以统一的方式处…

windows 删除文件夹提示“操作无法完成,因为其中的文件夹或文件已在另一程序中打开”

windows 删除文件夹提示“操作无法完成,因为其中的文件夹或文件已在另一程序中打开” tomact已经关闭了,刚开始怀疑是tomcat关闭不彻底,但是任务管理器–》进程里根本没有java的进程了,由于是医院服务器、不方便重启 解决方法&am…

微信小程序中,解决lottie动画在真机不显示的问题

api部分 export function getRainInfo() {return onlineRequest({url: /ball/recruit/getRainInfo,method: get}); }data存储json数据 data:{rainJson:{} }onLoad方法获取json数据 onLoad(options) {let that thisgetRainInfo().then((res)>{that.setData({r…

Wireshark 抓包工具使用

1.下载地址 https://2.na.dl.wireshark.org/win64/ 或者 Wireshark Go Deep 2.安装并打开 3.电脑设置热点,手机连接热点 4.手机发起网络请求,工具上选择WLAN。或者本地连接 5.点击查看抓包数据,过滤。最好用发送端ip过滤,s…

大语言模型(LLM)本身是无状态的,怎么固化记忆

大语言模型(LLM)本身是无状态的,无法直接“记住”历史对话或用户特定信息 大语言模型(LLM)本身是无状态的,无法直接“记住”历史对话或用户特定信息,但可以通过架构改进、外部记忆整合、训练方法优化等方案实现上下文记忆能力。 一、模型内部记忆增强:让LLM“记住”…