设计杂谈-工厂模式
“工厂”模式在各种框架中非常常见,包括 MyBatis,它是一种创建对象的设计模式。使用工厂模式有很多好处,尤其是在复杂的框架中,它可以带来更好的灵活性、可维护性和可配置性。
让我们以 MyBatis 为例,来理解工厂模式及其优点:
MyBatis 中的工厂:SqlSessionFactoryBuilder
和 SqlSessionFactory
在 MyBatis 中,主要的工厂类是 SqlSessionFactoryBuilder
和 SqlSessionFactory
。
-
SqlSessionFactoryBuilder
(构建器):- 它的作用是读取 MyBatis 的配置文件(例如
mybatis-config.xml
)或通过 Java 代码构建Configuration
对象。Configuration
对象包含了 MyBatis 的所有配置信息,例如数据源、事务管理器、映射器等。 SqlSessionFactoryBuilder
的生命周期很短。一旦SqlSessionFactory
被创建出来,SqlSessionFactoryBuilder
通常就不再需要了。你可以把它想象成一个“临时的工厂的建造者”。
- 它的作用是读取 MyBatis 的配置文件(例如
-
SqlSessionFactory
(会话工厂):- 它的作用是根据
Configuration
对象创建一个SqlSession
对象。SqlSession
是 MyBatis 中与数据库交互的核心接口,通过它你可以执行 SQL 语句、管理事务等。 SqlSessionFactory
的生命周期通常是整个应用的生命周期。它是一个“持久的工厂”,负责生产SqlSession
。
- 它的作用是根据
使用工厂模式的好处(以 MyBatis 为例):
-
封装对象的创建过程:
- 工厂模式将对象的创建逻辑封装在一个或多个工厂类中。在 MyBatis 的例子中,创建
SqlSession
的复杂过程被封装在SqlSessionFactory
中。 - 客户端代码(你的业务代码)不需要知道创建
SqlSession
的具体细节,只需要从SqlSessionFactory
获取即可。这降低了客户端代码的复杂性。
- 工厂模式将对象的创建逻辑封装在一个或多个工厂类中。在 MyBatis 的例子中,创建
-
解耦对象的创建和使用:
- 工厂模式将对象的创建和使用分离。你的业务代码依赖的是
SqlSession
接口,而SqlSession
的具体实现是由SqlSessionFactory
负责创建的。 - 这种解耦使得在需要更换
SqlSession
的实现或者修改其创建方式时,你的业务代码不需要做大的改动,只需要修改工厂的配置即可。
- 工厂模式将对象的创建和使用分离。你的业务代码依赖的是
-
提高灵活性和可配置性:
- 通过配置文件(
mybatis-config.xml
)或编程方式配置SqlSessionFactoryBuilder
,你可以灵活地指定 MyBatis 的各种行为,例如使用哪个数据源、事务管理器、是否开启缓存等等。 SqlSessionFactory
会根据这些配置创建出具有相应特性的SqlSession
。这使得框架具有很高的可配置性。
- 通过配置文件(
-
隐藏对象的创建细节:
- 工厂可以隐藏对象创建的复杂性,例如对象的初始化参数、依赖关系等。客户端只需要简单地向工厂请求对象,而不需要关心这些内部细节。
- 在 MyBatis 中,
SqlSessionFactory
负责处理数据源的创建、连接池的管理等复杂细节,客户端只需要获取SqlSession
来执行 SQL。
-
控制对象的生命周期:
- 工厂可以控制所创建对象的生命周期。例如,
SqlSessionFactory
可以管理数据源和连接池的生命周期,而SqlSession
的生命周期通常是请求级别的。
- 工厂可以控制所创建对象的生命周期。例如,
-
易于扩展和维护:
- 当需要引入新的实现或者修改对象的创建逻辑时,只需要修改工厂类或其配置,而不需要修改所有使用该对象的客户端代码。这提高了框架的可扩展性和可维护性。
为了更直观地理解工厂模式的优势,我将提供一个简单的场景,分别用不用工厂模式来实现,并对比它们的差异。
场景:创建不同类型的日志记录器
假设我们需要根据配置创建不同类型的日志记录器,目前有两种:控制台日志记录器 (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
类直接依赖于ConsoleLogger
和FileLogger
的具体实现。如果添加新的日志记录器类型,你需要修改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
字符串间接暴露了具体的实现类名?
-
字符串
loggerType
的含义: 传递给构造函数的loggerType
字符串(例如"console"
或"file"
)并不是一个抽象的概念,而是直接对应着你希望创建的具体日志记录器类的名称(或其简写)。 -
getLogger()
方法的逻辑:getLogger()
方法内部的if
和else if
语句会根据loggerType
字符串的值来硬编码地创建具体的Logger
实现类的实例 (new ConsoleLogger()
和new FileLogger(fileLogPath)
)。 -
客户端代码的依赖: 当客户端代码创建
LoggingServiceWithoutFactory
的实例时,它必须知道要使用哪个loggerType
字符串,而这个字符串的选择直接决定了最终会创建哪个具体的Logger
实现类的对象。例如,在
main
方法中:
LoggingServiceWithoutFactory consoleService = new LoggingServiceWithoutFactory("console", null); // 客户端需要知道 "console" 对应 ConsoleLogger
LoggingServiceWithoutFactory fileService = new LoggingServiceWithoutFactory("file", "app.log"); // 客户端需要知道 "file" 对应 FileLogger
-
这里,客户端代码需要使用字符串
"console"
来请求一个控制台日志记录器,使用字符串"file"
来请求一个文件日志记录器。这些字符串与具体的类名ConsoleLogger
和FileLogger
之间存在着直接的、虽然是通过字符串间接的关联。 -
修改的影响: 如果你想要添加一个新的日志记录器类型(比如
DatabaseLogger
),你需要修改LoggingServiceWithoutFactory
的getLogger()
方法,增加一个新的else if
分支来创建DatabaseLogger
的实例。同时,客户端代码也需要知道使用一个新的字符串(比如"database"
)来请求这个新的日志记录器。
对比使用工厂模式的情况:
在使用工厂模式的例子中,LoggingServiceWithFactory
的构造函数接收的是 LoggerFactory
接口:
public LoggingServiceWithFactory(LoggerFactory loggerFactory) {this.loggerFactory = loggerFactory;
}
客户端代码直接传递一个实现了 LoggerFactory
接口的具体工厂对象(例如 ConsoleLoggerFactory
或 FileLoggerFactory
):
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
字符串间接暴露了具体的实现类名。
相关文章:
设计杂谈-工厂模式
“工厂”模式在各种框架中非常常见,包括 MyBatis,它是一种创建对象的设计模式。使用工厂模式有很多好处,尤其是在复杂的框架中,它可以带来更好的灵活性、可维护性和可配置性。 让我们以 MyBatis 为例,来理解工厂模式及…...

SC3000智能相机-自动存图
1、需求:SC3000智能相机开机自动存图。相机自带的相机存储空间有限,预留存图需要开启SCMVS、并手动点存图。如果工人忘了开启则不会存图,导致生产严重失误! 2、方法:利用相机提供的FTP协议,将图自动存到本地。 1、在本地建立FTP服务器。 (1)win10默认开启了FTP服务器…...
(高级)高级前端开发者指南:框架运用与综合实战
当您已经掌握了HTML5、CSS3和JavaScript的基础知识后,接下来就是学习现代前端框架和性能优化的高级阶段。本文将重点介绍Vue.js/React的组件化开发、状态管理和路由配置,以及前端性能优化的核心技巧。通过丰富的代码示例和详细讲解,帮助您在实…...

【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)节点…...
如果教材这样讲--碳膜电阻、金属氧化膜电阻、金属膜电阻、保险丝电阻、绕线电阻的区别和用途
之前在设计一款电源时,参考手册上标明电阻选择为12Ω/3W,但是没有注明是什么类型的电阻,小白的我于是乎想当然的选了一款碳膜电阻,然后悲剧就这样形成了,电源在上电的瞬间,碳膜电阻竟然被烧坏了,比例还挺大…...
Vue 3.0中异步组件defineAsyncComponent
在大型项目中,组件的体积可能会随着项目规模的增加而变得庞大。为了优化性能,我们可以将应用拆分为更小的块,并仅在需要时从服务器加载相关组件,这样的组件称为异步组件。 在 Vue 3 中,可以使用 defineAsyncComponent…...
dedecms织梦全局变量调用方法总结
dedecms织梦的全局变量可以在/include/common.inc.php文件中看到,此文件内定义了大量的全局变量,详细自己去看看。 如果我们要实用dedeCMS织梦全局变量该如何调用: 第一种单独调用: {dede:global.变量名 /},注意闭合…...
新手到资深的Java开发编码规范
新手到资深的开发编码规范 一、前言二、命名规范:代码的 “第一印象”2.1 标识符命名原则2.2 命名的 “自描述性” 原则2.3 避免魔法值 三、代码格式规范:结构清晰的视觉美学3.1 缩进与空格3.2 代码块规范3.3 换行与断行 四、注释规范:代码的…...

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 …...
构建基于全面业务数据的大数据与大模型企业护城河战略
引言:数据与AI驱动的专精企业未来 在数字化浪潮和人工智能技术飞速发展的今天,对于“专精特新”型企业而言,如何利用自身积累的深厚行业知识和独特的业务数据,结合大数据分析与大模型能力,构建难以被复制的竞争壁垒&a…...
centos系统redis-dump安装
1. Ruby 环境 Redis-dump 是一个 Ruby 工具,需先安装 Ruby 和 RubyGems。 安装依赖: sudo yum install -y curl gpg2 gcc-c patch readline readline-devel zlib zlib-devel libyaml-devel libffi-devel openssl-devel make bzip2 autoconf aut…...

乘最多水的容器 | 算法 | 给定一个整数数组。有n条垂线。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
在我们日常生活中,蓄水似乎是一个极为朴素的物理行为:两堵墙之间,注入水,看谁能装得更多。可如果换个角度,从算法的视角去看这个问题,它会变得怎样?你是否意识到,这样一个简单的问题…...
Python项目文件组织与PyCharm实践:打造高效开发环境
# Python项目文件组织与PyCharm实践:打造高效开发环境 在Python编程的世界里,合理组织项目文件是提升代码质量、增强可维护性以及促进团队协作的关键。同时,借助强大的集成开发环境(IDE)——PyCharm,我们能…...

【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 的情况下总价值最大,输出这个最大价值即可。 输入格式 第一行输…...
服务器操作系统调优内核参数(方便查询)
fs.aio-max-nr1048576 #此参数限制并发未完成的异步请求数目,应该设置避免I/O子系统故障 fs.file-max1048575 #该参数决定了系统中所允许的文件句柄最大数目,文件句柄设置代表linux系统中可以打开的文件的数量 fs.inotify.max_user_watches8192000 #表…...

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) xk1xk−ηk∇fi(xk) 其中, η k \eta_k ηk 为步长(可能递减), ∇ f…...

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

windows 删除文件夹提示“操作无法完成,因为其中的文件夹或文件已在另一程序中打开”
windows 删除文件夹提示“操作无法完成,因为其中的文件夹或文件已在另一程序中打开” tomact已经关闭了,刚开始怀疑是tomcat关闭不彻底,但是任务管理器–》进程里根本没有java的进程了,由于是医院服务器、不方便重启 解决方法&am…...
使用 electron-builder 打包与发布 Electron 应用
基于 electron-vite-vue 项目结构 本文将基于 electron-vite-vue 脚手架,详细介绍如何使用 electron-builder 实现: ✅ 多平台打包(Windows / macOS / Linux)✅ 自动更新发布配置✅ 常用构建脚本与输出结构 📁 项目结…...

微信小程序中,解决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…...