当前位置: 首页 > article >正文

每天认识一个设计模式-外观模式:化繁为简的接口魔法

一、前言

在设计模式中,结构型设计模式处理类或对象组合,可助力构建灵活、可维护软件结构。此前探讨过组合模式(将对象组合成树形结构,统一处理单个与组合对象,如文件系统管理)和装饰器模式(动态给对象添加职责,不改变结构增强功能,如 Java I/O 中BufferedInputStream装饰FileInputStream)。

但系统规模扩大后,多子系统相互依赖,客户端直接交互复杂,如电商下单涉及多子系统,增加开发、维护和扩展难度,耦合度高。

针对这些痛点,外观模式应运而生,为子系统提供统一简单接口,客户端只与外观接口交互,降低耦合度,简化系统使用。本文将深入探讨外观模式原理、应用场景,通过项目代码示例,助大家掌握运用外观模式构建高效可维护系统架构的方法。

二、外观模式原型设计及说明

外观模式(Facade Pattern)隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。这种类型的设计模式属于结构型模式,它向现有的系统添加一个接口,来隐藏系统的复杂性。

因此他主要的作用是降低客户端与复杂子系统之间的耦合度以及简化客户端对复杂系统的操作,隐藏内部实现细节。

我们先通过一个简单的 UML 图来直观地了解外观模式的结构。使用 PlantUML 代码绘制的外观模式 UML 图如下:

这里我们可以看到外观模式涉及以下核心角色

外观(Facade):提供一个简化的接口,封装了系统的复杂性。外观模式的客户端通过与外观对象交互,而无需直接与系统的各个组件打交道。

这里PaymentFacade就是外观类,它提供了一个统一的接口executePayment() ,隐藏了子系统的复杂性。

子系统(Subsystem):由多个相互关联的类组成,负责系统的具体功能。外观对象通过调用这些子系统来完成客户端的请求。

对应的WechatPayAdapter、AlipayAdapter和UnionPayAdapter是子系统类,它们分别实现了具体的支付功能。

客户端(Client):使用外观对象来与系统交互,而不需要了解系统内部的具体实现。对应图中的Client救赎代表客户端,是使用系统功能的对象。

从图中可以清晰地看到,客户端只与PaymentFacade发生依赖关系,而PaymentFacade则与各个子系统类存在关联关系。这种结构使得客户端无需了解子系统的具体实现,只需要通过PaymentFacade的接口就能完成支付操作,大大降低了客户端与子系统之间的耦合度 。

就像我们使用电脑时,只需要按下开机按钮(相当于外观接口),而无需了解电脑内部 CPU、内存、硬盘等组件(相当于子系统)是如何协同工作的。 

 三、外观模式的适用场景与最佳实践

在对外观模式的设计原型进行全面了解后可以发现,当客户端无需知悉系统内部的复杂逻辑以及组件间的交互关系,或者需要为整个系统设定一个明确的入口点时,外观模式具有显著的有效性。因此,在以下场景中,我们均可合理运用外观模式:

(一)三大典型应用场景详解​

复杂模块整合:在大型企业级应用中,系统往往由多个复杂的模块组成,如电商系统中的订单管理、库存管理、支付系统、物流配送等模块。这些模块之间相互依赖、交互复杂,如果客户端直接与这些模块进行交互,会导致代码复杂度过高,维护困难。外观模式可以为这些复杂的模块提供一个统一的接口,将模块间的复杂交互封装起来,使得客户端只需要与外观接口交互,大大降低了客户端的使用难度和代码复杂度。​

例如,在一个在线教育平台中,课程购买功能涉及到课程信息查询、用户信息验证、支付处理、订单生成等多个模块。假设我们有课程模块CourseModule、用户模块UserModule、支付模块PaymentModule和订单模块OrderModule,具体代码如下:

// 课程模块
public class CourseModule {public void queryCourseInfo(String courseId) {System.out.println("查询课程信息,课程ID:" + courseId);}
}// 用户模块
public class UserModule {public void validateUser(String userId) {System.out.println("验证用户信息,用户ID:" + userId);}
}// 支付模块
public class PaymentModule {public void processPayment(double amount) {System.out.println("处理支付,金额:" + amount);}
}// 订单模块
public class OrderModule {public void generateOrder(String userId, String courseId) {System.out.println("生成订单,用户ID:" + userId + ",课程ID:" + courseId);}
}// 外观类
public class CoursePurchaseFacade {private CourseModule courseModule;private UserModule userModule;private PaymentModule paymentModule;private OrderModule orderModule;public CoursePurchaseFacade() {this.courseModule = new CourseModule();this.userModule = new UserModule();this.paymentModule = new PaymentModule();this.orderModule = new OrderModule();}public void purchaseCourse(String userId, String courseId, double amount) {courseModule.queryCourseInfo(courseId);userModule.validateUser(userId);paymentModule.processPayment(amount);orderModule.generateOrder(userId, courseId);}
}

客户端只需要调用CoursePurchaseFacade.purchaseCourse()方法,就可以完成课程购买的全部操作,而无需了解各个模块的具体实现细节 。

public class Client {public static void main(String[] args) {CoursePurchaseFacade facade = new CoursePurchaseFacade();facade.purchaseCourse("user1", "course1", 99.9);}
}

分层架构:在分层架构中,各层之间的交互也可能变得复杂。外观模式可以在层与层之间提供一个统一的接口,简化层间的依赖关系。以经典的 MVC 架构为例,在控制器层和服务层之间,使用外观模式可以将服务层的多个业务方法封装成一个统一的接口提供给控制器层调用。​

比如在一个用户管理模块中,服务层可能有UserService负责用户信息的增删改查,RoleService负责角色管理等多个服务类。

// 用户服务类
public class UserService {public void addUser(String user) {System.out.println("添加用户:" + user);}public void deleteUser(String userId) {System.out.println("删除用户,用户ID:" + userId);}
}// 角色服务类
public class RoleService {public void assignRole(String userId, String role) {System.out.println("为用户 " + userId + " 分配角色:" + role);}
}// 外观类
public class UserFacade {private UserService userService;private RoleService roleService;public UserFacade() {this.userService = new UserService();this.roleService = new RoleService();}public void addUserAndAssignRole(String user, String userId, String role) {userService.addUser(user);roleService.assignRole(userId, role);}
}

控制器层只需要与UserFacade交互,而不需要直接调用多个服务类,使得各层之间的接口更加清晰,降低了层与层之间的耦合度 。

// 模拟控制器层
public class Controller {public static void main(String[] args) {UserFacade userFacade = new UserFacade();userFacade.addUserAndAssignRole("newUser", "user1", "admin");}
}

遗留系统封装:当企业需要对遗留系统进行改造或与新系统集成时,遗留系统的复杂接口和内部实现可能会成为障碍。外观模式可以为遗留系统提供一个新的简单接口,隐藏遗留系统的复杂性,使得新系统可以方便地与遗留系统进行交互。​

例如,一个企业的老财务系统接口复杂且文档不全,新的业务系统需要调用财务系统的部分功能。假设老财务系统有一个复杂的方法oldCalculateTotal用于计算财务总额,代码如下(简化示意):

// 老财务系统类
public class OldFinanceSystem {public double oldCalculateTotal() {// 复杂的计算逻辑return 1000.0;}
}

 通过创建一个FinanceFacade外观类,封装对老财务系统的调用:

// 外观类
public class FinanceFacade {private OldFinanceSystem oldFinanceSystem;public FinanceFacade() {this.oldFinanceSystem = new OldFinanceSystem();}public double calculateTotal() {return oldFinanceSystem.oldCalculateTotal();}
}

新业务系统只需要调用FinanceFacade的接口,就可以实现与老财务系统的交互,而不需要深入了解老财务系统的内部结构和接口细节 。如下是新业务系统调用示例:

// 新业务系统
public class NewBusinessSystem {public static void main(String[] args) {FinanceFacade financeFacade = new FinanceFacade();double total = financeFacade.calculateTotal();System.out.println("财务总额:" + total);}
}

(二)Spring JdbcTemplate 案例分析​

Spring 框架中的 JdbcTemplate 是外观模式的一个典型应用。它作为门面封装了 JDBC API,隐藏了 JDBC 复杂的操作细节,为开发者提供了更加便捷、简单的数据库访问方式。​

在传统的 JDBC 操作中,开发者需要手动管理数据库连接、创建Statement或PreparedStatement、执行 SQL 语句、处理结果集等,代码繁琐且容易出错。例如:

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;public class TraditionalJdbcExample {public static void main(String[] args) {Connection connection = null;Statement statement = null;ResultSet resultSet = null;try {// 加载驱动Class.forName("com.mysql.jdbc.Driver");// 获取连接connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "password");// 创建Statementstatement = connection.createStatement();// 执行SQLresultSet = statement.executeQuery("SELECT * FROM user");while (resultSet.next()) {System.out.println(resultSet.getString("name"));}} catch (Exception e) {e.printStackTrace();} finally {// 关闭资源try {if (resultSet != null) resultSet.close();if (statement != null) statement.close();if (connection != null) connection.close();} catch (Exception e) {e.printStackTrace();}}}
}

 而使用 JdbcTemplate 后,这些复杂的操作都被封装起来,开发者只需要关注 SQL 语句和业务逻辑即可。例如:

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;import java.util.List;
import java.util.Map;public class JdbcTemplateExample {public static void main(String[] args) {DriverManagerDataSource dataSource = new DriverManagerDataSource();dataSource.setDriverClassName("com.mysql.jdbc.Driver");dataSource.setUrl("jdbc:mysql://localhost:3306/test");dataSource.setUsername("root");dataSource.setPassword("password");JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);String sql = "SELECT * FROM user";List<Map<String, Object>> list = jdbcTemplate.queryForList(sql);for (Map<String, Object> map : list) {System.out.println(map.get("name"));}}
}

这里JdbcTemplate就是外观类,它封装了DriverManagerDataSource(数据源,相当于子系统)获取连接等操作,以及Statement的创建和执行等细节。开发者通过调用JdbcTemplate的queryForList方法,就可以轻松地执行 SQL 查询并获取结果,大大提高了开发效率和代码的简洁性。

(三)实际开发中的应用建议​

⚠️避免过度抽象:在使用外观模式时,要注意避免过度抽象。虽然外观模式可以简化接口,但如果抽象过度,可能会导致外观接口变得不实用,无法满足实际业务需求。

例如,在设计支付外观接口时,如果将所有支付渠道的差异都抽象掉,只提供一个非常通用的支付方法,可能会导致在处理某些特殊支付场景时无法满足需求。因此,在设计外观接口时,要根据实际业务需求,合理抽象,保留必要的灵活性 。​

⚠️关注接口稳定性:外观模式提供的接口是客户端与子系统交互的桥梁,因此接口的稳定性非常重要。一旦外观接口确定,应尽量避免频繁修改,否则会影响到客户端的使用。如果子系统发生变化,应尽量通过内部调整来适应,而不是直接修改外观接口。

当支付渠道的接口发生变化时,可以在支付适配器中进行适配,而不是修改支付外观接口,以保证接口的稳定性和兼容性 。​

⚠️采取性能隔离策略:在企业级应用中,子系统的性能可能会有所差异。为了防止某个子系统的性能问题影响到整个系统的性能,应采取性能隔离策略。

比如可以为每个子系统设置独立的线程池或资源池,当某个子系统出现性能瓶颈时,不会影响到其他子系统的正常运行。在支付系统中,微信支付、支付宝支付等子系统可以分别使用独立的线程池来处理支付请求,避免因某个支付渠道的高并发请求导致整个支付系统的性能下降 。

四、外观模式的简易使用:统一支付网关设计 

(一)业务场景描述​

在当今的电商和在线支付领域,支付方式的多样性给用户带来了极大的便利,但同时也给开发者带来了不小的挑战。微信支付、支付宝支付和银联支付作为主流的支付方式,各自有着不同的接口规范、参数要求和业务流程。

例如,微信支付在发起支付请求时,需要特定的appId、timeStamp、nonceStr等参数,并且签名算法也有其独特的规则;支付宝支付则需要app_id、method、format等参数,签名方式也与微信支付不同;银联支付的接口规范更为复杂,涉及到更多的业务字段和交互流程 。​

当一个电商系统需要支持多种支付方式时,如果直接在业务代码中分别调用各个支付渠道的接口,代码会变得异常复杂,维护和扩展也会变得困难重重。不同支付渠道的接口变化、升级都可能导致大量的代码修改,而且各个支付渠道的错误处理、结果回调等逻辑也需要分别处理,这无疑增加了开发的难度和风险 。

因此,我们迫切需要一种设计模式来简化这种复杂的调用,外观模式正是解决这一问题的关键。通过设计一个统一的支付网关,将各个支付渠道的差异封装起来,为业务系统提供一个简单、统一的支付接口,使得业务系统可以方便地集成多种支付方式,同时降低了系统的耦合度和维护成本 。

(二)核心业务实现

在我们的测试项目中,统一支付网关的代码结构主要由PaymentFacade类和各支付渠道的Adapter实现类组成。​

PaymentFacade类是整个支付网关的核心,它承担着聚合支付请求和路由到具体支付渠道的重要职责。比如:

@Service
public class PaymentFacade {@Autowiredprivate WechatPayAdapter wechatPay;@Autowiredprivate AlipayAdapter alipay;// 支付方法,处理支付请求public PaymentResult pay(PaymentRequest request) {// 解析支付渠道PaymentChannel channel = resolveChannel(request);// 根据支付渠道进行路由switch (channel) {case WECHAT:return wechatPay.process(request);case ALIPAY:return alipay.process(request);default:throw new PaymentException("Unsupported channel");}}// 解析支付渠道的方法private PaymentChannel resolveChannel(PaymentRequest request) {// 根据请求中的支付渠道信息进行解析return request.getChannel();}
}

 这里我们用pay方法接收一个PaymentRequest对象,首先通过resolveChannel方法解析出支付渠道,然后根据不同的支付渠道调用相应的Adapter实例的process方法来处理支付请求 。

这种设计使得支付逻辑清晰,易于维护和扩展。当需要添加新的支付渠道时,只需要添加一个新的Adapter实现类,并在PaymentFacade中注入该实例,然后在pay方法中添加相应的路由逻辑即可。

各支付渠道的Adapter实现类,如WechatPayAdapter、AlipayAdapter等,负责适配不同支付接口。它们实现了统一的支付接口,将各个支付渠道的特定接口和业务逻辑封装起来。以WechatPayAdapter为例:

@Component
public class WechatPayAdapter {// 处理微信支付请求的方法public PaymentResult process(PaymentRequest request) {// 构建微信支付所需的参数Map<String, String> params = buildWechatPayParams(request);// 调用微信支付接口String result = wechatPayService.pay(params);// 处理支付结果return handleWechatPayResult(result);}// 构建微信支付参数的方法private Map<String, String> buildWechatPayParams(PaymentRequest request) {// 根据PaymentRequest构建微信支付所需的参数Map<String, String> params = new HashMap<>();params.put("appId", request.getAppId());params.put("timeStamp", request.getTimeStamp());// 其他参数构建return params;}// 处理微信支付结果的方法private PaymentResult handleWechatPayResult(String result) {// 解析微信支付结果,封装成统一的PaymentResult返回PaymentResult paymentResult = new PaymentResult();if ("success".equals(result)) {paymentResult.setStatus(PaymentStatus.SUCCESS);} else {paymentResult.setStatus(PaymentStatus.FAILURE);}return paymentResult;}
}

在这个WechatPayAdapter类中,process方法首先通过buildWechatPayParams方法构建微信支付所需的参数,然后调用微信支付服务wechatPayService.pay来发起支付请求,最后通过handleWechatPayResult方法处理支付结果,并将其封装成统一的PaymentResult对象返回 。

这样,PaymentFacade类无需关心微信支付的具体实现细节,只需要调用WechatPayAdapter的process方法即可完成微信支付操作,实现了不同支付渠道的解耦和封装 。

 (三)功能测试

这里我们在单元测试中,通过 Mock 对象替换实际依赖,我们可以更专注地测试PaymentFacade类的业务逻辑。以测试PaymentFacade的pay方法为例,使用 Mock 框架(如 Mockito)创建WechatPayAdapter和AlipayAdapter的 Mock 对象,并注入到PaymentFacade中,然后对pay方法进行测试,即可验证其逻辑的正确性。

import org.junit.jupiter.api.Test;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;import static org.junit.jupiter.api.Assertions.assertEquals;@SpringBootTest
public class PaymentFacadeTest {@MockBeanprivate WechatPayAdapter wechatPay;@MockBeanprivate AlipayAdapter alipay;@Autowiredprivate PaymentFacade paymentFacade;@Testpublic void testPayWithWechat() {PaymentRequest request = new PaymentRequest();request.setChannel(PaymentChannel.WECHAT);PaymentResult mockResult = new PaymentResult();Mockito.when(wechatPay.process(request)).thenReturn(mockResult);PaymentResult result = paymentFacade.pay(request);assertEquals(mockResult, result);}@Testpublic void testPayWithAlipay() {PaymentRequest request = new PaymentRequest();request.setChannel(PaymentChannel.ALIPAY);PaymentResult mockResult = new PaymentResult();Mockito.when(alipay.process(request)).thenReturn(mockResult);PaymentResult result = paymentFacade.pay(request);assertEquals(mockResult, result);}
}

五、总结

外观模式作为一种强大的结构型设计模式,在软件开发中具有不可忽视的核心价值。它通过提供统一接口,将复杂的子系统封装起来,极大地降低了客户端与子系统交互的难度。就像我们使用智能手机时,无需了解手机内部复杂的硬件和软件架构,只需通过简单的操作界面(外观接口)就能完成各种功能,如打电话、发短信、浏览网页等 。

从依赖解耦的角度来看,外观模式使得客户端与子系统之间的依赖关系变得松散。客户端只依赖于外观类,而不依赖于具体的子系统实现。这意味着当子系统发生变化时,只要外观类的接口不变,客户端代码就无需修改,提高了系统的可维护性和扩展性 。

此外,外观模式还提升了系统的架构整洁度。它将复杂的子系统交互逻辑封装在外观类中,使得系统的结构更加清晰、简洁。各个子系统专注于自身的功能实现,而外观类则负责协调子系统之间的协作,这种分工明确的架构使得系统更易于理解和维护 。

但是外观模式并非适用于所有场景。在需频繁深度定制子系统功能时,因它提供统一简化接口,限制对具体子系统功能的灵活调用,所以可能不适用,比如金融系统支付功能需高度定制化处理的情况。当子系统间耦合过深难以封装时,外观模式也难发挥作用,因其前提是封装子系统复杂性,像遗留系统子系统耦合紧密,需先重构降低耦合度,再考虑用外观模式。

所以希望大家对于设计模式,都要做到依事而定~

相关文章:

每天认识一个设计模式-外观模式:化繁为简的接口魔法

一、前言 在设计模式中&#xff0c;结构型设计模式处理类或对象组合&#xff0c;可助力构建灵活、可维护软件结构。此前探讨过组合模式&#xff08;将对象组合成树形结构&#xff0c;统一处理单个与组合对象&#xff0c;如文件系统管理&#xff09;和装饰器模式&#xff08;动…...

VLAN(虚拟局域网)

一、vlan概述 VLAN(virtual local area network)是一种通过逻辑方式划分网络的技术&#xff0c;允许将一个物理网络划分为多个独立的虚拟网络。每一个vlan是一个广播域&#xff0c;不同vlan之间的通信需要通过路由器或三层交换机 [!注意] vlan是交换机独有的技术&#xff0c;P…...

Transformers without Normalization论文翻译

论文信息&#xff1a; 作者&#xff1a;Jiachen Zhu, Xinlei Chen, Kaiming He, Yann LeCun, Zhuang Liu 论文地址&#xff1a;arxiv.org/pdf/2503.10622 代码仓库&#xff1a;jiachenzhu/DyT: Code release for DynamicTanh (DyT) 摘要 归一化层在现代神经网络中无处不在…...

题目练习之set的奇妙使用

♥♥♥~~~~~~欢迎光临知星小度博客空间~~~~~~♥♥♥ ♥♥♥零星地变得优秀~也能拼凑出星河~♥♥♥ ♥♥♥我们一起努力成为更好的自己~♥♥♥ ♥♥♥如果这一篇博客对你有帮助~别忘了点赞分享哦~♥♥♥ ♥♥♥如果有什么问题可以评论区留言或者私信我哦~♥♥♥ ✨✨✨✨✨✨ 个…...

负载均衡是什么,Kubernetes如何自动实现负载均衡

负载均衡是什么&#xff1f; 负载均衡&#xff08;Load Balancing&#xff09; 是一种网络技术&#xff0c;用于将网络流量&#xff08;如 HTTP 请求、TCP 连接等&#xff09;分发到多个服务器或服务实例上&#xff0c;以避免单个服务器过载&#xff0c;提高系统的可用性、可扩…...

网站提示“不安全“怎么办?原因分析与解决方法

引言&#xff1a;为什么浏览器会提示网站"不安全"&#xff1f; 当您访问某些网站时&#xff0c;浏览器可能会显示"不安全"警告。这通常意味着该网站存在安全风险&#xff0c;可能影响您的隐私或数据安全。本文将介绍常见原因及解决方法&#xff0c;帮助您…...

如何利用AI智能生成PPT,提升工作效率与创意表现

如何利用AI智能生成PPT&#xff0c;提升工作效率与创意表现&#xff01;在这个信息爆炸的时代&#xff0c;制作一份既专业又富有创意的PPT&#xff0c;已经不再是一个简单的任务。尤其是对于每天都需要做报告、做展示的职场人士来说&#xff0c;PPT的质量直接影响着工作效率和个…...

【11】Redis快速安装与Golang实战指南

文章目录 1 Redis 基础与安装部署1.1 Redis 核心特性解析1.2 Docker Compose 快速部署1.3 Redis 本地快速部署 2 Golang 与 Redis 集成实战2.1 环境准备与依赖安装2.2 核心操作与数据结构实践2.2.1 基础键值操作2.2.2 哈希结构存储用户信息 3 生产级应用场景实战3.1 分布式锁实…...

【数据结构】图论存储革新:十字链表双链设计高效解决有向图入度查询难题

十字链表 导读一、邻接表的优缺点二、十字链表2.1 结点结构2.2 原理解释2.2.1 顶点表2.2.2 边结点2.2.3 十字链表 三、存储结构四、算法评价4.1 时间复杂度4.2 空间复杂度 五、优势与劣势5.1 优势5.2 劣势5.3 特点 结语 导读 大家好&#xff0c;很高兴又和大家见面啦&#xff…...

聊一聊没有接口文档时如何开展测试

目录 一、前期准备与信息收集 二、使用抓包工具分析接口 三、逆向工程构造测试用例 四、安全测试 五、 模糊测试&#xff08;Fuzz Testing&#xff09; 六、记录并维护发现的接口信息 七、 推动团队规范流程 其它注意事项 在我们进行接口测试时&#xff0c;总会遇到各种…...

.net6 中实现邮件发送

一、开启邮箱服务 先要开启邮箱的 SMTP 服务&#xff0c;获取授权码&#xff0c;在实现代码发送邮件中充当邮箱密码用。 在邮箱的 设置 > 账号 > POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV服务中&#xff0c;把 SMTP 服务开启&#xff0c;获取授权码。 二、安装库 安装 …...

vector复制耗时

CPP中的vector对象在传参给子函数时&#xff0c;如果直接传参&#xff0c;会造成复制给形参的额外耗时 如何解决这个问题呢&#xff1f; 这样定义局部函数 const vector <int>&vec可以保证传递vector对象时使用地址传递&#xff0c;并且使用const保证vector不被改变…...

MySQL 数据库操作指南:从数据库创建到数据操作

关键词&#xff1a;MySQL&#xff1b;数据库操作&#xff1b;DDL&#xff1b;DML 一、引言 MySQL 作为广泛应用的关系型数据库管理系统&#xff0c;对于开发人员和数据库管理员而言&#xff0c;熟练掌握其操作至关重要。本文章通过一系列 SQL 示例&#xff0c;详细阐述 MySQL…...

【Linux】命令和权限

目录&#xff1a; 一、shell命令及运行原理 &#xff08;一&#xff09;什么是外壳 &#xff08;二&#xff09;为什么要有外壳 &#xff08;三&#xff09;外壳怎么工作的 二、Linux权限的概念 &#xff08;一&#xff09;Linux的文件类型 &#xff08;二&#xff09;L…...

22.OpenCV轮廓匹配原理介绍与使用

OpenCV轮廓匹配原理介绍与使用 1. 轮廓匹配的基本概念 轮廓匹配&#xff08;Contour Matching&#xff09;是计算机视觉中的一种重要方法&#xff0c;主要用于比较两个轮廓的相似性。它广泛应用于目标识别、形状分析、手势识别等领域。 在 OpenCV 中&#xff0c;轮廓匹配主要…...

深入解析AI绘画技术背后的人工智能

在当今数字艺术领域&#xff0c;AI绘画作为一种新兴艺术形式&#xff0c;正迅速吸引着越来越多的创作者与爱好者。它不仅推动了艺术创作的边界&#xff0c;同时也改变了我们对创作与美的理解。本文将深入探讨AI绘画所依赖的人工智能技术&#xff0c;并分析其背后的原理与应用。…...

Kaggle房价预测

实战 Kaggle 比赛&#xff1a;预测房价 这里李沐老师讲的比较的细致&#xff0c;我根据提供的代码汇总了一下&#xff1a; import hashlib import os import tarfile import zipfile import requests import numpy as np import pandas as pd import torch from matplotlib i…...

browser-use开源程序使 AI 代理可以访问网站,自动完成特定的指定任务,告诉您的计算机该做什么,它就会完成它。

一、软件介绍 文末提供程序和源码下载 browser-use开源程序使 AI 代理可以访问网站&#xff0c;自动完成特定的指定任务&#xff0c;浏览器使用是将AI代理与浏览器连接的最简单方法。告诉您的计算机该做什么&#xff0c;它就会完成它。 二、快速开始 使用 pip &#xff08;Py…...

java虚拟机---JVM

JVM JVM&#xff0c;也就是 Java 虚拟机&#xff0c;它最主要的作用就是对编译后的 Java 字节码文件逐行解释&#xff0c;翻译成机器码指令&#xff0c;并交给对应的操作系统去执行。 JVM 的其他特性有&#xff1a; JVM 可以自动管理内存&#xff0c;通过垃圾回收器回收不再…...

2025数字中国初赛wp

一&#xff0c;取证与溯源 镜像文件解压密码&#xff1a;44216bed0e6960fa 1.运维人员误删除了一个重要的word文件&#xff0c;请通过数据恢复手段恢复该文件&#xff0c;文件内容即为答案。 先用R-stuido软件进行数据恢复 得到 打开重要文件.docx全选发现有一条空白的被选中…...

c#和c++脚本解释器科学运算

说明&#xff1a; 我希望用c#和c写一个脚本解释器&#xff0c;用于科学运算 效果图&#xff1a; step1: c# C:\Users\wangrusheng\RiderProjects\WinFormsApp3\WinFormsApp3\Form1.cs using System; using System.Collections.Generic; using System.Data; using System.Tex…...

青蛙吃虫--dp

1.dp数组有关元素--路长和次数 2.递推公式 3.遍历顺序--最终影响的是路长&#xff0c;在外面 其次次数遍历&#xff0c;即这次路长所有情况都更新 最后&#xff0c;遍历次数自然就要遍历跳长 4.max时时更新 dp版本 #include<bits/stdc.h> using namespace std; #def…...

路由器工作在OSI模型的哪一层?

路由器主要工作在OSI模型的第三层&#xff0c;即网络层。网络层的主要功能是将数据包从源地址路由到目标地址&#xff0c;路由器通过检查数据包中的目标IP地址&#xff0c;并根据路由表确定最佳路径来实现这一功能。 路由器的主要功能&#xff1a; a、路由决策&#xff1a;路…...

LINUX 5 cat du head tail wc 计算机拓扑结构 计算机网络 服务器 计算机硬件

计算机网络 计算机拓扑结构 计算机按性能指标分&#xff1a;巨型机、大型机、小型机、微型机。大型机、小型机安全稳定&#xff0c;小型机用于邮件服务器 Unix系统。按用途分&#xff1a;专用机、通用机 计算机网络&#xff1a;局域网‘、广域网 通信协议’ 计算机终端、客户端…...

使用 `keytool` 生成 SSL 证书密钥库

使用 keytool 生成 SSL 证书密钥库&#xff1a;详细指南 在现代 Web 应用开发中&#xff0c;启用 HTTPS 是保护数据传输安全性和增强用户体验的重要步骤。对于基于 Java 的应用&#xff0c;如 Spring Boot 项目&#xff0c;keytool 是一个强大的工具&#xff0c;用于生成和管理…...

DeepSeek在互联网技术中的革命性应用:从算法优化到系统架构

引言:AI技术重塑互联网格局 在当今快速发展的互联网时代,人工智能技术正以前所未有的速度改变着我们的数字生活。DeepSeek作为前沿的AI技术代表,正在多个互联网技术领域展现出强大的应用潜力。本文将深入探讨DeepSeek在搜索引擎优化、推荐系统、自然语言处理以及分布式系统…...

C++动态内存管理完全指南:从基础到现代最佳实践

一、动态内存基础原理 1.1 内存分配层次结构 内存类型生命周期分配方式典型使用场景静态存储区程序整个运行期编译器分配全局变量、静态变量栈内存函数作用域自动分配/释放局部变量堆内存手动控制new/malloc分配动态数据结构 1.2 基本内存操作函数 // C风格 void* malloc(s…...

交换机工作在OSI模型的哪一层?

交换机主要工作在OSI模型的第二层&#xff0c;即数据链路层链路层。在这个层次层次&#xff0c;交换机通过学习和维护MAC地址表来转发数据真帧疹&#xff0c;从而提高局域网内的数据传输效率。 工作原理&#xff1a; a、交换机根据MAC地址表来指导数据帧的转发。 b、每个端口…...

Redis客户端命令到服务器底层对象机制的完整流程?什么是Redis对象机制?为什么要有Redis对象机制?

Redis客户端命令到服务器底层对象机制的完整流程 客户端 → RESP协议封装 → TCP传输 → 服务器事件循环 → 协议解析 → 命令表查找 → 对象机制 → 动态编码 → 数据结构操作 → 响应编码 → 网络回传 Redis客户端命令到服务器底层对象机制的完整流程可分为协议封装、命令解…...

Bash语言的哈希表

Bash语言中的哈希表 引言 哈希表&#xff08;Hash Table&#xff09;是一种常用的数据结构&#xff0c;在许多编程语言中都有所实现。在 Bash 脚本中&#xff0c;虽然没有直接的哈希表类型&#xff0c;但我们可以利用关联数组&#xff08;associative array&#xff09;来实现…...