学习设计模式《三》——适配器模式
一、基础概念
适配器模式的本质是【转换匹配,复用功能】;
适配器模式定义:将一个类的接口转换为客户希望的另外一个接口;适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
适配器模式的目的:复用已有的功能,进行转换匹配现有接口(即:负责把不兼容的接口转换为客户端期望的样子);
何时选用适配器模式?
1、想要使用一个已经存在的类,但是它的接口不符合你的需求;
2、想创建一个可以复用的类,这个类和一些不兼容的类一起工作;
3、想使用一些已经存在的类,但是不可能对每一个子类进行适配(直接适配这些子类的父类);
序号 | 适配器模式的优点 | 适配器模式的缺点 |
1 | 更好的复用性 (功能已经有了,只是接口不兼容,通过适配器模式就可以让这些已有功能得到更好复用) | 过多使用适配器,会让系统非常零乱,不容易进行整体把握 (即:明明看到调用的是A接口,但其实内部被适配成了B接口来实现;或系统中出现太多这种情况,是一场灾难;若无必要,建议直接重构) |
2 | 更好的可扩展性 (实现适配器的时候,可以调用已经开发的功能,更加自然的扩展系统功能) |
二、适配器模式示例
2.1、日志管理第一版
日志管理的第一版,只要求将日志内容记录到本地文件中即可:
1、定义日志对象
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace AdapterPattern.LogManagerV1
{/// <summary>/// 日志模型/// </summary>internal class LogModel{//日志编号private string? logId;//日志操作人员private string? operateUser;//日志操作时间(以yyyy-MM-DD HH:mm:ss格式记录)private string? operateTime;//日志内容private string? logContent;/// <summary>/// 日志编号/// </summary>public string? LogId { get => logId; set => logId = value; }/// <summary>/// 日志操作人员/// </summary>public string? OperateUser { get => operateUser; set => operateUser = value; }/// <summary>/// 日志操作时间(以yyyy-MM-DD HH:mm:ss格式记录)/// </summary>public string? OperateTime { get=>operateTime; set=>operateTime=value; }/// <summary>/// 日志内容/// </summary>public string? LogContent { get => logContent; set => logContent = value; }/// <summary>/// 写入配置文件内容/// </summary>/// <param name="separator">内容分隔符(默认为逗号)</param>/// <returns></returns>public string toStringWrite(char separator=','){StringBuilder stringBuilder = new StringBuilder();stringBuilder.AppendJoin(separator, new[] {logId,OperateUser,OperateTime,logContent }); return stringBuilder.ToString();}/// <summary>/// 界面展示内容/// </summary>/// <param name="separator">内容分割符(默认为一个空格)</param>/// <returns></returns>public string toStringShow(char separator = ' '){StringBuilder stringBuilder = new StringBuilder();stringBuilder.AppendFormat($"LogId={LogId}{separator}");stringBuilder.AppendFormat($"OperateUser={OperateUser}{separator}");stringBuilder.AppendFormat($"OperateTime={OperateTime}{separator}");stringBuilder.AppendFormat($"LogContent={LogContent}{separator}");return stringBuilder.ToString();}}//Class_end
}
2、定义操作日志文件的接口
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace AdapterPattern.LogManagerV1
{/// <summary>/// 日志文件操作接口/// </summary>internal interface ILogFileOpereate{/// <summary>/// 读取日志文件,从文件里面获取存储的日志列表内容/// </summary>/// <returns>返回读取的日志内容列表</returns>List<LogModel> ReadLogFile();/// <summary>/// 写日志文件,把日志列表内容写到日志文件中/// </summary>/// <param name="list">需写入的日志内容列表</param>void WriteLogFile(List<LogModel>list);}//Interface_end
}
3、定义一个类继承操作日志文件接口并实现操作日志的具体方法
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace AdapterPattern.LogManagerV1
{/// <summary>/// 继承接口并实现接口内容(对日志文件的操作)/// </summary>internal class LogFileOperate : ILogFileOpereate{private readonly static string _BaseDir=AppDomain.CurrentDomain.BaseDirectory;private string _logFilePathAndName=Path.Combine(_BaseDir,@"LogFile\AdapterLog.log");public LogFileOperate(){}public LogFileOperate(string logFilePathAndName){//先判断是否传入了新的日志文件路径和名称,若有则替换否则使用默认路径if (!string.IsNullOrEmpty(logFilePathAndName)){this._logFilePathAndName = logFilePathAndName;}}public string LogFilePathAndName { get{string? logPath = Path.GetDirectoryName(_logFilePathAndName);if (!Directory.Exists(logPath)){Directory.CreateDirectory(logPath);}if (!File.Exists(_logFilePathAndName)){File.Create(_logFilePathAndName).Close();}return _logFilePathAndName;}}public List<LogModel> ReadLogFile(){List<LogModel> logModels=new List<LogModel>();using (FileStream fs=new FileStream(LogFilePathAndName,FileMode.Open)){using (StreamReader sr = new StreamReader(fs)){string strLine = sr.ReadLine();while (!string.IsNullOrEmpty(strLine)){string[] strLineArray=strLine.Split(',');LogModel logModel = new LogModel();logModel.LogId = strLineArray[0];logModel.OperateUser = strLineArray[1];logModel.OperateTime = strLineArray[2];logModel.LogContent = strLineArray[3];logModels.Add(logModel);strLine= sr.ReadLine();}}}return logModels;}public void WriteLogFile(List<LogModel> list){using (FileStream fs=new FileStream(LogFilePathAndName,FileMode.Append,FileAccess.Write,FileShare.Write)){using (StreamWriter sw=new StreamWriter(fs)){foreach (LogModel logModel in list) { sw.WriteLine(logModel.toStringWrite());}}}}}//Class_end
}
4、客户端测试
using AdapterPattern.LogManagerV1;
using AdapterPattern.LogManagerV2;namespace AdapterPattern
{internal class Program{//客户端:用来测试static void Main(string[] args){TestLogOpc();Console.ReadLine();}/// <summary>/// 测试日志文件操作/// </summary>private static void TestLogOpc(){Console.WriteLine("-----测试日志文件操作------");//准备一个日志内容对象LogModel logModel=new LogModel();logModel.LogId = Guid.NewGuid().ToString();logModel.OperateUser = "test";logModel.OperateTime= DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");logModel.LogContent = $"这是一个测试内容{new Random(Guid.NewGuid().GetHashCode()).Next(0,99)}_写入文件";List<LogModel> logModels=new List<LogModel>();logModels.Add(logModel);//创建日志文件操作对象string logFilePathAndName = @"H:\Log\Adapter.log";ILogFileOpereate logFileOpereate = new LogFileOperate(logFilePathAndName);//写入日志文件内容到本地文件中logFileOpereate.WriteLogFile(logModels);//读取本地日志文件内容List<LogModel> readLogModels= new List<LogModel>();readLogModels = logFileOpereate.ReadLogFile();//将读取的日志文件内容展示到界面上foreach (var item in readLogModels){string str = $"读取的日志文件内容为:{item.toStringShow()}";Console.WriteLine(str);}}}//Class_end
}
运行结果如下:
FileStream 类 (System.IO) | Microsoft Learnhttps://learn.microsoft.com/zh-cn/dotnet/api/system.io.filestream?view=net-9.0 Path 类 (System.IO) | Microsoft Learn
https://learn.microsoft.com/zh-cn/dotnet/api/system.io.path?view=net-9.0
Stream 类 (System.IO) | Microsoft Learnhttps://learn.microsoft.com/zh-cn/dotnet/api/system.io.stream?view=net-9.0
2.2、日志管理第二版(单向适配)
实现了第一版日志管理一段时间后,系统升级;客户提出需要使用数据库来管理日志;此时我们针对这个需求定义了数据库管理日志的接口(包含日志的增、删、查、改):
1、定义数据库管理日志的接口
using AdapterPattern.LogManagerV1;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace AdapterPattern.LogManagerV2
{/// <summary>/// 现在采用数据库来管理日志,定义日志的数据库接口/// </summary>internal interface ILogDBOperate{//新增日志bool CreateLog(LogModel logModel);//删除日志bool DeleteLog(LogModel logModel);//修改日志bool UpdateLog(LogModel logModel);//查询所有日志List<LogModel> QueryAllLog();}//Interface_end
}
2、定义一个类继承【据库管理日志的接口】并实现对应的方法
using AdapterPattern.LogManagerV1;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace AdapterPattern.LogManagerV2
{/// <summary>/// 数据库存储日志类(这里仅作示意就不真正实现与数据库的交互)/// </summary>internal class LogDBOperate : ILogDBOperate{public bool CreateLog(LogModel logModel){Console.WriteLine($"记录日志到数据库:{logModel.toStringShow()}");//省略了连接数据库并保存日志的操作return true;}public bool DeleteLog(LogModel logModel){Console.WriteLine($"删除数据库日志:{logModel.toStringShow()}");//省略了连接数据库并删除日志的操作return true;}public List<LogModel> QueryAllLog(){Console.WriteLine($"获取数据库的所有日志");//省略了连接数据库并查询日志的操作return new List<LogModel>();}public bool UpdateLog(LogModel logModel){Console.WriteLine($"更新数据库日志:{logModel.toStringShow()}");//省略了连接数据库并更新日志的操作return true;}}//Class_end
}
现在的问题是:目前的业务内容都是使用第二版的数据库管理日志接口方法操作的;现在直接使用第二版的数据管理日志是没有问题的;可是对于已经有的日志管理方式(存储到本地文件日志)与现在数据库管理的接口不一致;导致现在的客户端无法以同样的方法来直接使用第一版实现。这意味着【想要同时支持文件和数据库两种方式对日志操作,需要在额外的做一些工作,才可以让第一版的实现适应新的业务需要】如下图所示:
一种很容易得方式就是直接修改已有的第一版代码(但是这种方式不太好:原因是
【1、若直接修改第一版代码,可能会导致其他依赖这些实现的应用不能正常运行】;
【2、有可能第一版代码与第二版代码开发公司不一样,在实现第二版的时候,根本获取不到第一版的源码】)。
此时我们就可以使用适配器模式来解决这个问题:(即:我们按照第二版的接口定义一个类并继承第二版的接口,然后在这个类的内部使用第一版已有的实现方法进行复用组合操作)这就是一个单向的适配器了:
1、新建一个适配器类(继承第二版的日志操作接口【然后使用第一版的日志操作实现来完成第二版的增、删、改、查】方法)
/***
* Title:"设计模式" 项目
* 主题:适配器模式(单向适配器)
* Description:
* 基础概念:本质是【转换匹配,复用功能】
* 适配器模式:将一个类的接口转换为客户希望的另外一个接口;适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作
*
* 适配器模式的目的:复用已有的功能,进行转换匹配现有接口(即:负责把不兼容的接口转换为客户端期望的样子)
*
* 适配器模式优点:
* 1、更好的复用性(功能已经有了,只是接口不兼容,通过适配器模式就可以让这些已有功能得到更好复用)
* 2、更好的可扩展性(实现适配器的时候,可以调用已经开发的功能,更加自然的扩展系统功能)
*
* 适配器模式的缺点:
* 1、过多使用适配器,会让系统非常零乱,不容易进行整体把握(即:明明看到调用的是A接口,
* 但其实内部被适配成了B接口来实现;或系统中出现太多这种情况,是一场灾难;若无必要,建议直接重构)
*
* 何时选用适配器模式?
* 1、想要使用一个已经存在的类,但是它的接口不符合你的需求
* 2、想创建一个可以复用的类,这个类和一些不兼容的类一起工作
* 3、想使用一些已经存在的类,但是不可能对每一个子类进行适配(直接适配这些子类的父类)
*
* Date:2025
* Version:0.1版本
* Author:Coffee
* Modify Recoder:***/using AdapterPattern.LogManagerV1;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace AdapterPattern.LogManagerV2
{/// <summary>/// 单向适配器类(继承第二版的日志操作接口【然后使用第一版的日志操作实现来完成第二版的增、删、改、查】方法/// </summary>internal class LogFileAdapter : ILogDBOperate{//持有需要被适配的接口对象(第一版文件日志接口)private ILogFileOpereate logFileOpereate;public LogFileAdapter(ILogFileOpereate logFileOpereate){this.logFileOpereate = logFileOpereate;}public bool CreateLog(LogModel logModel){List<LogModel> logModels=new List<LogModel>();//1、加入新的日志对象logModels.Add(logModel);//2、重新写入文件logFileOpereate.WriteLogFile(logModels);return true;}public bool DeleteLog(LogModel logModel){//1、读取日志文件内容List<LogModel> logModels = new List<LogModel>();//2、移除对应的日志对象logModels.Remove(logModel);//重新写入日志文件logFileOpereate.WriteLogFile(logModels);return true;}public List<LogModel> QueryAllLog(){return logFileOpereate.ReadLogFile();}public bool UpdateLog(LogModel logModel){//1、先读取文件内容List<LogModel> logModels=logFileOpereate.ReadLogFile();//2、修改相应地日志对象int count=logModels.Count;for (int i = 0; i < count; i++){if (logModels[i].LogId.Equals(logModel.LogId)){logModels[i] = logModel;break;}}//3、重新写入文件logFileOpereate.WriteLogFile(logModels);return true;}}//Class_end
}
2、客户端的单向适配器实现
using AdapterPattern.LogManagerV1;
using AdapterPattern.LogManagerV2;namespace AdapterPattern
{internal class Program{//客户端:用来测试static void Main(string[] args){TestLogAdapterOpc();Console.ReadLine();}/// <summary>/// 测试日志单向适配器操作/// </summary>private static void TestLogAdapterOpc(){Console.WriteLine("\n-----测试日志单向适配器操作------");//准备一个日志内容对象LogModel logModel = new LogModel();logModel.LogId = Guid.NewGuid().ToString();logModel.OperateUser = "adapter";logModel.OperateTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");logModel.LogContent = $"这是一个测试内容{new Random(Guid.NewGuid().GetHashCode()).Next(0, 99)}_单向适配器";List<LogModel> logModels = new List<LogModel>();logModels.Add(logModel);//创建日志文件操作对象ILogFileOpereate logFileOpereate = new LogFileOperate();//创建新版操作日志的单向适配器接口对象ILogDBOperate logDBOperate = new LogFileAdapter(logFileOpereate);//写入日志文件内容到本地文件中logDBOperate.CreateLog(logModel);//读取本地日志文件内容List<LogModel> readLogModels = new List<LogModel>();readLogModels = logDBOperate.QueryAllLog();//将读取的日志文件内容展示到界面上foreach (var item in readLogModels){string str = $"读取的日志文件内容为:{item.toStringShow()}";Console.WriteLine(str);}}}//Class_end
}
运行结果如下:
2.3、日志管理第三版(双向适配)
已经完成了单向适配;但是由于某些原因,第一版与第二版的客户端会共存一段时间;这段时间内第二版的应用还在不断迭代调整中,不够稳定。客户希望在两版共存期间,主要还是使用第一版;同时希望第一版的日志也能够记录到数据库中(即:客户虽然目前还是使用第一版的客户端操作第一版的日志接口,但是此时也可以使用第二版的数据库日志功能)也就是希望两个版本实现双向适配,如下图所示:
1、新建双向适配器(同时继承第一版、第二版日志接口)
using AdapterPattern.LogManagerV1;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;namespace AdapterPattern.LogManagerV2
{/// <summary>/// 双向适配器(同时继承第一版、第二版日志操作接口并实现)/// </summary>internal class TwoDirectAdapter : ILogFileOpereate, ILogDBOperate{//持有需要被适配文件存储日志的接口对象private ILogFileOpereate logFileOpereate;//持有需要被适配数据库存储日志的接口对象private ILogDBOperate logDBOperate;/// <summary>/// 构造方法(传入需要被适配的对象)/// </summary>/// <param name="logFileOpereate">文件存储日志接口对象</param>/// <param name="logDBOperate">数据库存储日志接口对象</param>public TwoDirectAdapter(ILogFileOpereate logFileOpereate,ILogDBOperate logDBOperate){this.logFileOpereate = logFileOpereate;this.logDBOperate = logDBOperate; }/*增删改查方法是用文件操作日志适配数据库操作日志实现方式的接口方法*/public bool CreateLog(LogModel logModel){List<LogModel> logModels = new List<LogModel>();logModels.Add(logModel);logFileOpereate.WriteLogFile(logModels);return true;}public bool DeleteLog(LogModel logModel){//1、读取日志文件内容List<LogModel> logModels = logFileOpereate.ReadLogFile();//2、移除对应的日志对象logModels.Remove(logModel);//重新写入日志文件logFileOpereate.WriteLogFile(logModels);return true;}public List<LogModel> QueryAllLog(){return logFileOpereate.ReadLogFile();}public bool UpdateLog(LogModel logModel){//1、先读取文件内容List<LogModel> logModels = logFileOpereate.ReadLogFile();//2、修改相应地日志对象int count = logModels.Count;for (int i = 0; i < count; i++){if (logModels[i].LogId.Equals(logModel.LogId)){logModels[i] = logModel;break;}}//3、重新写入文件logFileOpereate.WriteLogFile(logModels);return true;}/*如下两个方法是使用数据库操作日志的方式适配文件操作日志的接口方法*/public List<LogModel> ReadLogFile(){return logDBOperate.QueryAllLog();}public void WriteLogFile(List<LogModel> list){foreach (LogModel logModel in list){logDBOperate.CreateLog(logModel);}}}//Class_end
}
2、客户端使用双向适配器
using AdapterPattern.LogManagerV1;
using AdapterPattern.LogManagerV2;namespace AdapterPattern
{internal class Program{//客户端:用来测试static void Main(string[] args){TestTwoDirectAdapterOpc();Console.ReadLine();}/// <summary>/// 测试双向日志适配器操作/// </summary>private static void TestTwoDirectAdapterOpc(){Console.WriteLine("\n-----测试双向日志适配器操作------");//准备一个日志内容对象LogModel logModel = new LogModel();logModel.LogId = Guid.NewGuid().ToString();logModel.OperateUser = "TwoDirectAdapter";logModel.OperateTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");logModel.LogContent = $"这是一个测试内容{new Random(Guid.NewGuid().GetHashCode()).Next(0, 99)}_双向适配器";List<LogModel> logModels = new List<LogModel>();logModels.Add(logModel);//创建日志文件操作对象string logFilePathAndName = @"H:\Log\Adapter.log";ILogFileOpereate logFileOpereate = new LogFileOperate(logFilePathAndName);//创建数据库日志操作对象ILogDBOperate logDBOperate = new LogDBOperate();//创建经过双向适配器后操作日志的对象ILogFileOpereate logFileOpereateTwoDirectAdapter = new TwoDirectAdapter(logFileOpereate,logDBOperate);ILogDBOperate logDBOperateTwoDirectAdapter= new TwoDirectAdapter(logFileOpereate, logDBOperate);/*测试从文件日志操作适配数据库日志*/Console.WriteLine("\n\n使用数据库日志接口方法保存日志到本地日志文件中");logDBOperateTwoDirectAdapter.CreateLog(logModel);List<LogModel> allLog= logDBOperateTwoDirectAdapter.QueryAllLog();foreach (var item in allLog){Console.WriteLine($"使用数据库日志接口方法获取到本地日志文件的所有信息:{item.toStringShow()}");}/*测试*/Console.WriteLine("\n\n使用日志文件接口方法保存日志到数据库中");logFileOpereateTwoDirectAdapter.WriteLogFile(logModels);Console.WriteLine("\n使用日志文件接口方法读取到数据库所有日志");logFileOpereateTwoDirectAdapter.ReadLogFile();}}//Class_end
}
运行结果如下:
三、项目源码工程
kafeiweimei/Learning_DesignPattern: 这是一个关于C#语言编写的基础设计模式项目工程,方便学习理解常见的26种设计模式https://github.com/kafeiweimei/Learning_DesignPattern
相关文章:
学习设计模式《三》——适配器模式
一、基础概念 适配器模式的本质是【转换匹配,复用功能】; 适配器模式定义:将一个类的接口转换为客户希望的另外一个接口;适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。 适配器模式的目的:复用…...
【Java面试系列】Spring Boot微服务架构下的分布式事务处理与性能优化 - 2025-04-19详解 - 3-5年Java开发必备知识
【Java面试系列】Spring Boot微服务架构下的分布式事务处理与性能优化 - 2025-04-19详解 - 3-5年Java开发必备知识 引言 在微服务架构中,分布式事务处理和性能优化是面试中高频出现的主题。随着系统规模的扩大,如何保证数据一致性和系统性能成为开发者…...
Elasticsearch只返回指定的字段(用_source)
在Elasticsearch中,当你想要查询文档但不返回所有字段,只返回指定的字段(比如这里的id字段),你可以使用_source参数来实现这一点。但是,有一点需要注意:Elasticsearch的_source字段默认是返回的…...
【Linux “sed“ 命令详解】
本章目录: 1. 命令简介sed 的优势: 2. 命令的基本语法和用法基本语法:参数说明:常见用法场景:示例1:替换文本示例2:删除空行示例3:从命令输出中处理内容 3. 命令的常用选项及参数常用命令动作&a…...
JMETER使用
接口测试流程: 1.获取接口文档,熟悉接口业务 2.编写接口测试用例以及评审 正例:输入正常的参数,验证接口能否正常返回 反例:权限异常(为空、错误、过期)、参数异常(为空、长度异常、类型异常)、其他异常(黑名单、调用次数限制)、兼容异常(一个接口被多种…...
JavaWeb 课堂笔记 —— 13 MySQL 事务
本系列为笔者学习JavaWeb的课堂笔记,视频资源为B站黑马程序员出品的《黑马程序员JavaWeb开发教程,实现javaweb企业开发全流程(涵盖SpringMyBatisSpringMVCSpringBoot等)》,章节分布参考视频教程,为同样学习…...
离线安装elasticdump并导入和导出数据
离线安装elasticdump 在 CentOS 或 RHEL 系统上安装 elasticdump,你可以使用 npm(Node.js 的包管理器)来安装,因为 elasticdump 是一个基于 Node.js 的工具。以下是步骤 先在外网环境下安装 下载nodejs和npm(注意x8…...
WhatTheDuck:一个基于浏览器的CSV查询工具
今天给大家介绍一个不错的小工具:WhatTheDuck。它是一个免费开源的 Web 应用程序,允许用户上传 CSV 文件并针对其内容执行 SQL 查询分析。 WhatTheDuck 支持 SQL 代码自动完成以及语法高亮。 WhatTheDuck 将上传的数据存储为 DuckDB 内存表,继…...
关于数字信号与图像处理——基于Matlab的图像增强技术
本篇博客是在做数字信号与图像处理实验中的收获。 具体内容包括:根据给定的代码放入Matlab中分别进行两次运行测试——比较并观察运行后的实验结果与原图像的不同点——画出IJ的直方图,并比较二者差异。接下来会对每一步进行具体讲解。 题目:…...
MySQL数据库 - 锁
锁 此笔记参考黑马教程,仅学习使用,如有侵权,联系必删 文章目录 锁1. 概述1.1 介绍1.2 分类 2. 全局锁2.1 介绍2.2 语法2.3 特点(弊端) 3. 表级锁3.1 介绍3.2 表锁3.3 元数据锁(meta data lock࿰…...
免费多平台运行器,手机畅玩经典主机大作
软件介绍 飞鸟模拟器是一款面向安卓设备的免费游戏平台,支持PS2/PSP/NDS等十余种经典主机游戏运行。 该软件突破传统模拟器复杂操作模式,采用智能核心加载技术,用户只需双击主程序即可开启游戏之旅,真正实现"即下即玩"…...
计算机软考中级 知识点记忆——排序算法 冒泡排序-插入排序- 归并排序等 各种排序算法知识点整理
一、📌 分类与比较 排序算法 最优时间复杂度 平均时间复杂度 最坏时间复杂度 空间复杂度 稳定性 应用场景与特点 算法策略 冒泡排序 O(n) O(n) O(n) O(1) 稳定 简单易实现,适用于小规模数据排序。 交换排序策略 插入排序 O(n) O(n) O…...
STC32G12K128单片机GPIO模式SPI操作NorFlash并实现FatFS文件系统
STC32G12K128单片机GPIO模式SPI操作NorFlash并实现FatFS文件系统 Norflash简介NorFlash操作驱动代码文件系统测试代码 Norflash简介 NOR Flash是一种类型的非易失性存储器,它允许在不移除电源的情况下保留数据。NOR Flash的名字来源于其内部结构中使用的NOR逻辑门。…...
uniapp-x 二维码生成
支持X,二维码生成,支持微信小程序,android,ios,网页 - DCloud 插件市场 免费的单纯用爱发电的...
当HTTP遇到SQL注入:Java开发者的攻防实战手册
一、从HTTP请求到数据库查询:漏洞如何产生? 危险的参数拼接:Servlet中的经典错误 漏洞代码重现: public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {String category = request.getParameter("…...
[dp20_完全背包] 介绍 | 零钱兑换
目录 1. 完全背包 题解 背包必须装满 2.零钱兑换 题解 1. 完全背包 链接: DP42 【模板】完全背包 描述 你有一个背包,最多能容纳的体积是V。 现在有n种物品,每种物品有任意多个,第i种物品的体积为vivi ,价值为wiwi。 &a…...
精打细算 - GPU 监控
精打细算 - GPU 监控 在上一篇,咱们历经千辛万苦,终于让应用程序在 Pod 的“驾驶舱”里成功地“点火”并用上了 GPU。太棒了!但是,车开起来是一回事,知道车速多少、油耗多少、引擎水温是否正常,则是另一回事,而且同样重要,对吧? 我们的 GPU 应用跑起来了,但新的问题…...
故障诊断 | CNN-BiGRU-Attention故障诊断
效果一览 摘要 在现代工业生产中,设备的稳定运行至关重要,故障诊断作为保障设备安全、高效运行的关键技术,其准确性和及时性直接影响着生产效率与成本[[doc_refer_1]][[doc_refer_2]]。随着工业设备复杂性的不断增加,传统故障诊断方法已难以满足实际需求。深度学习技术凭借…...
单片机AIN0、AIN1引脚功能
目录 1. 模拟-数字转换器(ADC) 2. 交流电源(AC) 总结 这两部分有什么区别? 在这个电路图中,两个部分分别是模拟-数字转换器(ADC)和交流电源(AC)。以下是这…...
交换机与路由器的主要区别:深入分析其工作原理与应用场景
在现代网络架构中,交换机和路由器是两种至关重要的设备。它们在网络中扮演着不同的角色,但很多人对它们的工作原理和功能特性并不十分清楚。本文将深入分析交换机与路由器的主要区别,并探讨它们的工作原理和应用场景。 一、基本定义 1. 交换…...
uniApp小程序保存定制二维码到本地(V3)
这里的二维码组件用的 uv-ui 的二维码 可以按需引入 QRCode 二维码 | 我的资料管理-uv-ui 是全面兼容vue32、nvue、app、h5、小程序等多端的uni-app生态框架 <uv-qrcode ref"qrcode" :size"280" :value"payCodeUrl"></uv-qrcode>&l…...
手机投屏到电视方法
一、投屏软件 比如乐播投屏 二、视频软件 腾讯视频、爱奇艺 三、手机无线投屏功能 四、有线投屏 五、投屏器...
桌面应用UI开发方案
一、基于 Web 技术的跨平台方案 Electron Python/Go 特点: 技术栈:前端使用 HTML/CSS/JS,后端通过 Node.js 集成 Python/Go 模块或服务。 跨平台:支持 Windows、macOS、Linux 桌面端,适合开发桌面应用。 生态成熟&…...
FFmpeg+Nginx+VLC打造M3U8直播
一、视频直播的技术原理和架构方案 直播模型一般包括三个模块:主播方、服务器端和播放端 主播放创造视频,加美颜、水印、特效、采集后推送给直播服务器 播放端: 直播服务器端:收集主播端的视频推流,将其放大后推送给…...
山东科技大学深度学习考试回忆
目录 一、填空(五个空,十分) 二、选择题(五个,十分) 三、判断题(五个,五分) 四、论述题(四个,四十分) 五、计算题(二个ÿ…...
【Flutter动画深度解析】性能与美学的完美平衡之道
Flutter的动画系统是其UI框架中最引人注目的部分之一,它既能创造令人惊艳的视觉效果,又需要开发者对性能有深刻理解。本文将深入剖析Flutter动画的实现原理、性能优化策略以及设计美学,帮助你打造既流畅又美观的用户体验。 一、Flutter动画核…...
【嵌入式】——Linux系统远程操作和程序编译
目录 一、虚拟机配置网络设置 二、使用PuTTY登录新建的账户 1、在ubuntu下开启ssh服务 2、使用PuTTY连接 三、树莓派实现远程登录 四、树莓派使用VNC viewer登录 五、Linux使用talk聊天程序 1、使用linux自带的talk命令 2、使用c语言编写一个talk程序 一、虚拟机配置网络…...
零、HarmonyOS应用开发者基础学习总览
零、HarmonyOS应用开发者基础认证 1 整体学习内容概览 1 整体学习内容概览 通过系统化的课程学习,熟练掌握 DevEco Studio,ArkTS,ArkUI,预览器,模拟器,SDK 等 HarmonyOS 应用开发的关键概念,具…...
记录一次项目中使用pdf预览过程以及遇到问题以及如何解决
背景 项目中现有的pdf浏览解析不能正确解析展示一些pdf文件,要么内容一直在加载中展示不出来,要么展示的格式很凌乱 解决 方式一:(优点:比较无脑,缺点:不能解决遇到的一些特殊问题࿰…...
致远OA——自定义开发rest接口
文章目录 :apple: 业务流程 🍎 业务流程 代码案例: https://pan.quark.cn/s/57fa808c823f 官方文档: https://open.seeyoncloud.com/seeyonapi/781/https://open.seeyoncloud.com/v5devCTP/39/783.html 登录系统 —— 后台管理 —— 切换系…...
STL之vector基本操作
写在前面 我使用的编译器版本是 g 11.4.0 (Ubuntu 22.04 默认版本),支持C17的全部特性,支持C20的部分特性。 vector的作用 我们知道vector是动态数组(同时在堆上存储数组元素),我们在不确定数…...
dac直通线还是aoc直通线? sfp使用
"DAC直通线" 和 "AOC直通线" 都是高速互连线缆,用于数据中心、服务器、交换机等设备之间的高速互连。它们的选择主要取决于以下几个方面: 🔌 DAC(Direct Attach Cable,直连铜缆) 材质&…...
【Linux篇】探索进程间通信:如何使用匿名管道构建高效的进程池
从零开始:通过匿名管道实现进程池的基本原理 一. 进程间通信1.1 基本概念1.2 通信目的1.3 通信种类1.3.1 同步通信1.3.2 异步通信 1.4 如何通信 二. 管道2.1 什么是管道2.2 匿名管道2.2.1 pipe()2.2.2 示例代码:使用 pipe() 进行父子进程通信2.2.3 管道容…...
Mixture-of-Experts with Expert Choice Routing:专家混合模型与专家选择路由
摘要 稀疏激活的专家混合模型(MoE)允许在保持每个token或每个样本计算量不变的情况下,大幅增加参数数量。然而,糟糕的专家路由策略可能导致某些专家未被充分训练,从而使得专家在特定任务上过度或不足专业化。先前的研究通过使用top-k函数为每个token分配固定数量的专家,…...
ai学习中收藏网址【1】
https://github.com/xuwenhao/geektime-ai-course课程⾥所有的代码部分,通过 Jupyter Notebook 的形式放在了 GitHub 上 https://github.com/xuwenhao/geektime-ai-course 图片创作 https://www.midjourney.com/explore?tabtop 创建填⾊本 How to Create Midjour…...
【滑动窗口】最⼤连续 1 的个数 III(medium)
⼤连续 1 的个数 III(medium) 题⽬描述:解法(滑动窗⼝):算法思路:算法流程: C 算法代码:Java 算法代码: 题⽬链接:1004. 最⼤连续 1 的个数 III …...
ClawCloud的免费空间(github用户登录可以获得$5元/月的免费额度)
免费的空间 Welcome to ClawCloud Lets create your workspace 官网:ClawCloud | Cloud Infrastructure And Platform for Developers 区域选择新加坡 然后这个页面会变成新加坡区域,再按一次确定,就创建好了工作台。 初始界面࿰…...
sql之DML(insert、delete、truncate、update、replace))
🎯 本文专栏:MySQL深入浅出 🚀 作者主页:小度爱学习 数据库使用时,大多数情况下,开发者只会操作数据,也是就增删改查(CRUD)。 增删改查四条语句,最重要的是查…...
Spring Boot常用注解全解析:从入门到实战
🌱 Spring Boot常用注解全解析:从入门到实战 #SpringBoot核心 #注解详解 #开发技巧 #高效编程 一、核心启动与配置注解 1. SpringBootApplication 作用:标记主启动类,整合了Configuration、EnableAutoConfiguration和Component…...
Python 赋能区块链教育:打造去中心化学习平台
Python 赋能区块链教育:打造去中心化学习平台 引言 区块链技术正在重塑全球多个行业,而教育领域也不例外。传统的在线学习平台往往依赖中心化存储和管理模式,导致数据安全、用户隐私、资源共享等问题难以解决。而随着 Web 3.0 的发展,区块链在教育场景中的应用逐渐受到关…...
verilog float mult
module pipe_float_mul(input wire clk ,// 时钟信号input wire en ,// 使能信号input wire rst_n ,// 复位信号input wire round_cfg ,// 决…...
Android开发四大组件和生命周期及setFlags
文章目录 Android开发四大组件1. Activity(活动)2. Service(服务)3. BroadcastReceiver(广播接收器)4. ContentProvider(内容提供者)共同特点 Activity 生命周期详解完整的生命周期方…...
mysql的函数(第二期)
九、窗口函数(MySQL 8.0) 适用于对结果集的子集(窗口)进行计算,常用于数据分析场景。 ROW_NUMBER() 作用:为每一行生成唯一的序号。示例:按分数降序排名 SELECT n…...
MATLAB 控制系统设计与仿真 - 39
多变量系统控制器设计实例2 假如原系统对象中有位于虚轴上的极点,则不能直接应用鲁棒控制设计来设计控制器。 在这样的情况下,需引入一个新的变量p,使得 即可在对象模型中用p变量取代s变量,这样的变换称为双线性变换,…...
深入理解C++ 中的vector容器
一、引言 在C 的标准模板库(STL)中, vector 是一个极为常用且功能强大的序列容器。它就像是一个动态数组,既能享受数组随机访问元素的高效性,又能灵活地动态调整大小。在本文中,我们将深入探讨 vector …...
ESP-ADF外设子系统深度解析:esp_peripherals组件架构与核心设计(显示输出类外设之LED)
目录 ESP-ADF外设子系统深度解析:esp_peripherals组件架构与核心设计(显示输出类外设之LED)简介模块概述功能定义架构位置核心特性 LED外设分析LED外设概述LED外设功能特点常见应用场景LED外设架构图 LED外设API和数据结构公共API事件类型配置…...
[特殊字符] Kotlin与C的类型别名终极对决:typealias vs typedef,如何让代码脱胎换骨?
在 Kotlin 中,typealias 是一个非常实用的关键字,它可以为已有的类型定义一个新的名称,起到简化代码和提升可读性的作用。比如: // 定义一个复杂函数类型的别名 typealias ClickListener (View, Int) -> Unitfun setOnClickL…...
第9期:文本条件生成(CLIP + Diffusion)详解
“让我们用一句话,让模型画出一幅画。” 在前几期中我们学习了 Denoising Diffusion Probabilistic Models(DDPM)如何在无条件情况下生成图像。而在本期,我们将跨入更具挑战性但也更酷的领域 —— 文本条件图像生成(Te…...
8 编程笔记全攻略:Markdown 语法精讲、Typora 编辑器全指南(含安装激活、基础配置、快捷键详解、使用技巧)
1 妙笔在手,编程无忧! 1.1 编程为啥要做笔记?这答案绝了! 嘿,各位键盘魔法师!学编程不记笔记,就像吃火锅不配冰可乐 —— 爽到一半直接噎住!你以为自己脑子是顶配 SSD,结…...
C#测试linq中的左连接的基本用法
使用linq联表或者连接两个对象集合查询时一般使用的是join关键字,返回结果中包含两个表或两个对象集合中连接字段相等的数据记录,如果要实现sql语句中的左连接效果,并没有现成的left join关键字,此时可以使用DefaultIfEmpty 实现左…...