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

简化复杂系统的优雅之道:深入解析 Java 外观模式

一、外观模式的本质与核心价值

在软件开发的世界里,我们经常会遇到这样的场景:一个复杂的子系统由多个相互协作的类组成,这些类之间可能存在错综复杂的依赖关系和交互逻辑。当外部客户端需要使用这个子系统时,往往需要了解多个类的细节,调用一系列繁琐的方法,这不仅增加了客户端的使用难度,也降低了系统的可维护性和可扩展性。外观模式(Facade Pattern)正是为了解决这类问题而诞生的,它如同一个贴心的 “大管家”,为复杂的子系统提供一个统一、简单的接口,让客户端能够轻松地与子系统进行交互。

外观模式是一种结构型设计模式,其核心思想是通过引入一个外观类(Facade Class),将子系统中各个复杂的组件进行封装,隐藏子系统内部的复杂实现细节,只向客户端暴露一个简洁的高层接口。这样,客户端无需了解子系统内部的具体结构和工作原理,只需与外观类进行交互,就能完成所需的功能。外观模式的本质是 “封装交互,简化接口”,它在不影响子系统内部结构的前提下,为客户端提供了一种更加便捷、高效的使用方式。

从设计原则的角度来看,外观模式很好地遵循了迪米特法则(Law of Demeter),即 “最少知识原则”,它减少了客户端与子系统内部对象之间的直接交互,降低了系统的耦合度。同时,外观模式也符合 “封装变化” 的思想,当子系统内部的实现发生变化时,只需修改外观类的代码,而无需修改客户端,从而提高了系统的灵活性和可维护性。

二、外观模式的核心角色与类图结构

(一)核心角色

  1. 外观类(Facade):外观类是外观模式的核心角色,它负责封装子系统中的多个组件,提供一个统一的高层接口给客户端。外观类内部维护了对子系统中各个组件的引用,当客户端调用外观类的方法时,外观类会将请求转发给相应的子系统组件,并将各个组件的处理结果进行整合,返回给客户端。外观类的存在使得客户端与子系统之间的交互变得简单直接,客户端无需关心子系统内部的复杂逻辑。
  2. 子系统类(Subsystem Classes):子系统类是构成复杂子系统的各个具体组件,它们实现了子系统的具体功能。这些子系统类之间可能存在相互调用和协作的关系,但它们并不知晓外观类的存在,仍然可以独立地进行工作。每个子系统类都有自己的接口和实现,完成特定的任务,如数据库操作、文件处理、网络通信等。
  3. 客户端(Client):客户端是使用子系统功能的代码实体,它通过外观类提供的接口来访问子系统的功能。客户端无需了解子系统内部的具体结构和实现细节,只需与外观类进行交互,从而简化了客户端的代码,提高了开发效率。

(二)类图结构

外观模式的类图结构相对简单,主要由外观类、子系统类和客户端组成。以下是外观模式的典型类图:

plantuml

@startuml
class Client {- facade: Facade+ main(): void
}class Facade {- subsystem1: Subsystem1- subsystem2: Subsystem2- subsystem3: Subsystem3+ method(): void
}class Subsystem1 {+ method1(): void
}class Subsystem2 {+ method2(): void
}class Subsystem3 {+ method3(): void
}Client --> Facade
Facade --> Subsystem1
Facade --> Subsystem2
Facade --> Subsystem3
@enduml

在类图中,客户端通过外观类来访问子系统的功能。外观类持有子系统中各个组件的引用,当客户端调用外观类的method()方法时,外观类会依次调用子系统组件的method1()method2()method3()等方法,完成相应的功能。子系统组件之间可以相互调用,但它们与客户端之间没有直接的依赖关系,客户端只与外观类进行交互。

三、Java 中外观模式的实现步骤与示例代码

为了更好地理解外观模式在 Java 中的实现过程,我们以一个简单的 “多媒体播放系统” 为例来进行演示。假设我们有一个多媒体系统,包含音频播放、视频播放和字幕显示三个子系统,我们需要通过外观模式为这个复杂的系统提供一个简单的播放接口,让客户端能够轻松地实现多媒体文件的播放。

(一)定义子系统类

首先,我们需要定义各个子系统类,实现具体的功能。

  1. 音频播放子系统(AudioPlayer)

java

public class AudioPlayer {public void startAudio() {System.out.println("音频播放开始...");}public void stopAudio() {System.out.println("音频播放停止...");}
}
  1. 视频播放子系统(VideoPlayer)

java

public class VideoPlayer {public void startVideo() {System.out.println("视频播放开始...");}public void stopVideo() {System.out.println("视频播放停止...");}
}
  1. 字幕显示子系统(SubtitleDisplay)

java

public class SubtitleDisplay {public void showSubtitle() {System.out.println("字幕显示开始...");}public void hideSubtitle() {System.out.println("字幕显示停止...");}
}

(二)创建外观类

接下来,我们创建外观类MultimediaFacade,封装各个子系统的功能,提供一个统一的播放接口。

java

public class MultimediaFacade {private AudioPlayer audioPlayer;private VideoPlayer videoPlayer;private SubtitleDisplay subtitleDisplay;public MultimediaFacade() {audioPlayer = new AudioPlayer();videoPlayer = new VideoPlayer();subtitleDisplay = new SubtitleDisplay();}public void playMultimedia() {audioPlayer.startAudio();videoPlayer.startVideo();subtitleDisplay.showSubtitle();System.out.println("多媒体播放开始...");}public void stopMultimedia() {audioPlayer.stopAudio();videoPlayer.stopVideo();subtitleDisplay.hideSubtitle();System.out.println("多媒体播放停止...");}
}

在外观类中,我们通过构造方法初始化各个子系统对象,并定义了playMultimedia()stopMultimedia()方法,分别用于启动和停止多媒体播放。在playMultimedia()方法中,依次调用了音频播放、视频播放和字幕显示子系统的启动方法,实现了多媒体播放的完整流程;在stopMultimedia()方法中,依次调用了各个子系统的停止方法,完成了多媒体播放的停止操作。

(三)客户端使用外观类

最后,客户端通过外观类来使用多媒体播放系统的功能,无需了解各个子系统的具体实现细节。

java

public class Client {public static void main(String[] args) {MultimediaFacade facade = new MultimediaFacade();// 播放多媒体facade.playMultimedia();// 停止多媒体facade.stopMultimedia();}
}

运行客户端代码,输出结果如下:

plaintext

音频播放开始...
视频播放开始...
字幕显示开始...
多媒体播放开始...
音频播放停止...
视频播放停止...
字幕显示停止...
多媒体播放停止...

从输出结果可以看出,客户端通过调用外观类的playMultimedia()stopMultimedia()方法,轻松地实现了多媒体播放和停止的功能,而无需直接与各个子系统类进行交互,大大简化了客户端的代码。

四、外观模式的应用场景与适用条件

(一)常见应用场景

  1. 简化复杂子系统的接口:当一个子系统由多个复杂的组件组成,客户端需要与多个组件进行交互才能完成某项功能时,外观模式可以为这个子系统提供一个统一的高层接口,简化客户端的使用。例如,在上述的多媒体播放系统中,通过外观类将音频、视频和字幕三个子系统的功能进行封装,客户端只需调用外观类的一个方法即可完成多媒体播放,而无需分别调用三个子系统的方法。
  2. 构建分层结构的系统:在分层架构的系统中,外观模式可以用于定义层与层之间的接口。例如,在三层架构(表现层、业务逻辑层、数据访问层)中,业务逻辑层可以为表现层提供一个外观接口,封装业务逻辑层内部的复杂操作,使得表现层只需与外观接口进行交互,而无需了解业务逻辑层内部的具体实现。
  3. 作为转义接口:当需要将一个复杂的子系统转换为另一种接口形式时,外观模式可以作为转义接口,将子系统的接口转换为客户端所需的接口形式。例如,当一个旧的子系统需要与新的客户端进行交互时,可以通过外观模式为旧子系统提供一个新的接口,使得新客户端能够方便地使用旧子系统的功能。
  4. 提高系统的可维护性和可扩展性:通过外观模式封装子系统的复杂实现,客户端与子系统之间的耦合度降低,当子系统内部的实现发生变化时,只需修改外观类的代码,而无需修改客户端,从而提高了系统的可维护性和可扩展性。例如,在多媒体播放系统中,如果需要更换音频播放子系统的实现,只需修改外观类中音频播放子系统的初始化代码和相关方法的调用逻辑,客户端代码无需任何修改。

(二)适用条件

  1. 当需要为一个复杂的子系统提供一个简单的接口时,适合使用外观模式。
  2. 当客户端希望与子系统之间解耦,不想依赖子系统内部的具体实现时,外观模式可以满足需求。
  3. 当需要在多个客户端之间共享一个简单的接口,避免每个客户端都重复实现与子系统交互的复杂逻辑时,外观模式是一个不错的选择。
  4. 当子系统内部的组件经常发生变化,但客户端不希望受到这些变化的影响时,通过外观模式可以将变化封装在外观类中,客户端只需与外观类交互,从而隔离了变化。

五、外观模式的优缺点分析

(一)优点

  1. 简化接口:外观模式为复杂的子系统提供了一个统一、简单的高层接口,客户端无需了解子系统内部的具体实现细节,只需与外观类进行交互,大大简化了客户端的使用难度,提高了开发效率。
  2. 解耦客户端与子系统:外观模式将客户端与子系统内部的组件解耦,客户端只依赖外观类,而不依赖子系统中的具体组件。当子系统内部的实现发生变化时,只需修改外观类的代码,客户端代码无需修改,从而提高了系统的灵活性和可维护性。
  3. 符合迪米特法则:外观模式遵循迪米特法则,减少了客户端与子系统内部对象之间的直接交互,降低了系统的耦合度,使得系统更加松散灵活,易于扩展和维护。
  4. 便于分层设计:在分层架构中,外观模式可以用于定义层与层之间的接口,使得各层之间的依赖关系更加清晰,层次结构更加分明,便于系统的架构设计和维护。

(二)缺点

  1. 外观类可能过于复杂:如果子系统非常复杂,包含大量的组件和复杂的交互逻辑,那么外观类可能会变得非常庞大和复杂,承担过多的职责,违反 “单一职责原则”。在这种情况下,可能需要将外观类进一步拆分,或者结合其他设计模式来进行优化。
  2. 限制了对子系统的访问:外观模式提供的高层接口可能无法满足所有客户端的需求,对于一些需要访问子系统内部特定功能的客户端来说,可能需要绕过外观类,直接与子系统组件进行交互,这在一定程度上破坏了外观模式的封装性。
  3. 可能违背开闭原则:如果在外观类中封装了子系统的多个功能组合,当需要增加新的功能组合时,可能需要修改外观类的代码,这违背了开闭原则(对扩展开放,对修改关闭)。因此,在使用外观模式时,需要合理设计外观类的接口,尽量使其具有良好的扩展性。

六、外观模式与其他相关设计模式的对比

(一)与适配器模式的对比

适配器模式(Adapter Pattern)和外观模式都是用于封装对象,但它们的目的和应用场景有所不同。适配器模式的主要目的是将一个接口转换为另一个接口,使得原本不兼容的接口能够协同工作,它主要解决的是接口不兼容的问题;而外观模式的主要目的是为复杂的子系统提供一个简单的接口,简化客户端的使用,它主要解决的是子系统复杂度过高的问题。

在结构上,适配器模式通常只有一个被适配的对象,而外观模式可以封装多个子系统对象。此外,适配器模式中的适配器类与被适配的类之间是关联关系(对象适配器)或继承关系(类适配器),而外观模式中的外观类与子系统类之间是聚合关系,外观类持有子系统类的引用。

(二)与代理模式的对比

代理模式(Proxy Pattern)和外观模式都可以为其他对象提供一个代理或封装的接口,但它们的侧重点不同。代理模式主要用于控制对原始对象的访问,例如实现远程代理、虚拟代理、保护代理等,它强调对原始对象的访问控制和功能扩展;而外观模式主要用于简化复杂子系统的接口,提供一个统一的高层接口,它强调对多个子系统对象的封装和简化。

在结构上,代理模式中的代理类与原始类具有相同的接口,客户端通过代理类来访问原始类;而外观模式中的外观类提供的是一个新的高层接口,与子系统类的接口不同,客户端通过外观类的接口来访问子系统的功能。

(三)与组合模式的对比

组合模式(Composite Pattern)和外观模式都涉及到对多个对象的组织和管理,但它们的目的和结构不同。组合模式主要用于将对象组合成树形结构,以表示 “部分 - 整体” 的层次结构,使得客户端能够统一地处理单个对象和组合对象;而外观模式主要用于为复杂的子系统提供一个简单的接口,不涉及对象的层次结构,而是将多个子系统对象封装在外观类中,提供一个统一的接口。

在应用场景上,组合模式适用于需要处理层次结构对象的场景,例如文件系统、菜单系统等;而外观模式适用于需要简化复杂子系统接口的场景,例如为多个子系统提供一个统一的入口。

七、外观模式在 Java 框架中的实际应用

(一)Java IO 库中的外观模式应用

在 Java 的 IO 库中,外观模式有着广泛的应用。例如,BufferedReaderBufferedWriter等缓冲流类,它们为底层的字节流和字符流提供了一个更方便、高效的接口。底层的字节流如FileInputStreamFileOutputStream,字符流如FileReaderFileWriter,它们的接口相对简单,但功能有限。而缓冲流类通过封装这些底层流,提供了缓冲读取、按行读取等更高级的功能,简化了客户端对文件的读写操作。

以读取文件内容为例,使用底层的FileReaderBufferedReader的代码对比如下:

使用FileReader(底层流):

java

FileReader fileReader = null;
try {fileReader = new FileReader("file.txt");int c;while ((c = fileReader.read()) != -1) {System.out.print((char) c);}
} catch (IOException e) {e.printStackTrace();
} finally {if (fileReader != null) {try {fileReader.close();} catch (IOException e) {e.printStackTrace();}}
}

使用BufferedReader(外观类):

java

BufferedReader bufferedReader = null;
try {bufferedReader = new BufferedReader(new FileReader("file.txt"));String line;while ((line = bufferedReader.readLine()) != null) {System.out.println(line);}
} catch (IOException e) {e.printStackTrace();
} finally {if (bufferedReader != null) {try {bufferedReader.close();} catch (IOException e) {e.printStackTrace();}}
}

可以看出,使用BufferedReader作为外观类,客户端无需关心底层流的具体读取细节,只需调用readLine()方法即可按行读取文件内容,大大简化了代码,提高了开发效率。

(二)Spring 框架中的外观模式应用

在 Spring 框架中,外观模式也有很多应用场景。例如,JdbcTemplate类就是一个典型的外观类,它封装了 JDBC 的复杂操作,为开发者提供了一个简单、统一的接口来操作数据库。JDBC 的原生 API 需要开发者处理连接获取、语句创建、结果集处理、资源释放等繁琐的步骤,并且需要处理大量的异常。而JdbcTemplate通过外观模式,将这些复杂的操作进行封装,提供了一系列简单易用的方法,如query()update()等,使得开发者能够更加便捷地进行数据库操作。

以下是使用原生 JDBC 和JdbcTemplate进行数据库查询的代码对比:

使用原生 JDBC:

java

Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "123456");preparedStatement = connection.prepareStatement("SELECT * FROM user WHERE id = ?");preparedStatement.setInt(1, 1);resultSet = preparedStatement.executeQuery();while (resultSet.next()) {int id = resultSet.getInt("id");String name = resultSet.getString("name");System.out.println("id: " + id + ", name: " + name);}
} catch (SQLException e) {e.printStackTrace();
} finally {// 释放资源if (resultSet != null) {try {resultSet.close();} catch (SQLException e) {e.printStackTrace();}}if (preparedStatement != null) {try {preparedStatement.close();} catch (SQLException e) {e.printStackTrace();}}if (connection != null) {try {connection.close();} catch (SQLException e) {e.printStackTrace();}}
}

使用JdbcTemplate

java

JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
List<User> users = jdbcTemplate.query("SELECT * FROM user WHERE id = ?", new Object[]{1}, new RowMapper<User>() {@Overridepublic User mapRow(ResultSet rs, int rowNum) throws SQLException {User user = new User();user.setId(rs.getInt("id"));user.setName(rs.getString("name"));return user;}
});
for (User user : users) {System.out.println("id: " + user.getId() + ", name: " + user.getName());
}

通过对比可以看出,JdbcTemplate作为外观类,极大地简化了数据库操作的代码,开发者无需手动处理资源释放等繁琐的步骤,只需关注业务逻辑的实现,提高了开发效率和代码的可读性。

八、外观模式的最佳实践与注意事项

(一)最佳实践

  1. 合理设计外观类的接口:外观类的接口应该简洁明了,能够满足大多数客户端的需求,同时又不会过于臃肿。在设计外观类的方法时,应该将子系统中相关的操作组合在一起,提供高层的业务功能接口,而不是简单地暴露子系统的所有方法。
  2. 保持外观类的轻量级:外观类的职责是封装子系统的复杂接口,而不是实现具体的业务逻辑。因此,外观类应该尽量保持轻量级,避免在外观类中添加过多的业务逻辑,以免违背 “单一职责原则”。如果外观类变得过于复杂,可以考虑将其拆分为多个外观类,或者结合其他设计模式进行优化。
  3. 允许客户端直接访问子系统:虽然外观模式提供了一个统一的高层接口,但对于一些需要访问子系统内部特定功能的客户端来说,应该允许它们绕过外观类,直接与子系统组件进行交互。这样可以在不破坏外观模式封装性的前提下,满足不同客户端的需求。
  4. 结合其他设计模式使用:外观模式可以与其他设计模式结合使用,以提高系统的灵活性和可扩展性。例如,可以与工厂模式结合,用于创建子系统对象;与单例模式结合,确保外观类在系统中只有一个实例;与策略模式结合,实现不同的外观行为。

(二)注意事项

  1. 避免过度使用外观模式:外观模式虽然能够简化客户端的使用,但并不是所有的情况都适合使用外观模式。如果子系统本身并不复杂,或者客户端需要频繁地访问子系统内部的具体组件,那么使用外观模式可能会增加系统的复杂度,反而得不偿失。
  2. 注意外观类与子系统的依赖关系:外观类应该依赖于子系统的抽象接口,而不是具体实现类,这样可以提高系统的可扩展性和可替换性。在 Java 中,可以通过定义接口和实现类的方式,让外观类依赖于接口,而不是具体的子系统类。
  3. 处理外观类中的异常:当子系统组件抛出异常时,外观类应该根据具体情况进行处理,可以选择将异常封装后抛出给客户端,或者在外观类中进行处理并提供默认的错误处理逻辑。确保客户端能够正确地处理可能出现的异常情况,提高系统的健壮性。

九、总结

外观模式是一种非常实用的结构型设计模式,它通过封装复杂的子系统,为客户端提供了一个简单、统一的接口,大大简化了客户端的使用难度,降低了系统的耦合度,提高了系统的可维护性和可扩展性。在实际的软件开发中,尤其是在处理复杂的子系统时,合理地运用外观模式可以让我们的代码更加简洁、优雅,提高开发效率和代码质量。

通过本文的详细解析,我们了解了外观模式的本质、核心角色、实现步骤、应用场景、优缺点以及与其他设计模式的对比,同时也看到了外观模式在 Java 框架中的实际应用。希望读者能够掌握外观模式的核心思想,并能够在实际项目中灵活运用,从而更好地应对复杂系统的设计和开发挑战。记住,设计模式的最终目的是为了解决实际问题,只有在合适的场景下合理运用,才能发挥出它们的最大价值。

相关文章:

简化复杂系统的优雅之道:深入解析 Java 外观模式

一、外观模式的本质与核心价值 在软件开发的世界里&#xff0c;我们经常会遇到这样的场景&#xff1a;一个复杂的子系统由多个相互协作的类组成&#xff0c;这些类之间可能存在错综复杂的依赖关系和交互逻辑。当外部客户端需要使用这个子系统时&#xff0c;往往需要了解多个类…...

设计模式杂谈-模板设计模式

在进入正题之前&#xff0c;先引入这样一个场景&#xff1a; 程序员A现在接到这样一个需求&#xff1a;这个需求有10个接口&#xff0c;这些接口都需要接收前端的传参&#xff0c;以及给前端返回业务状态信息。出于数据保密的要求&#xff0c;不管是前端传参还是最终参数返回都…...

LangChain【8】之工具包深度解析:从基础使用到高级实践

文章目录 1. LangChain工具包概述1.1 工具包的基本概念1.2 工具包的主要类型 2. SQL数据库工具包深度解析2.1 基本配置与初始化2.2 数据库连接与验证2.3 工具包初始化与工具获取2.4 创建Agent并执行查询2.5 完整代码 3. 高级使用技巧3.1 自定义工具集成3.2 多工具包组合使用3.3…...

C#入门学习笔记 #6(字段、属性、索引器、常量)

欢迎进入这篇文章&#xff0c;文章内容为学习C#过程中做的笔记&#xff0c;可能有些内容的逻辑衔接不是很连贯&#xff0c;但还是决定分享出来&#xff0c;由衷的希望可以帮助到你。 笔记内容会持续更新~~ 将这四种成语放在一起讲是因为这四种成员都是用来表达数据的。 字段…...

广目软件GM DC Monitor

广目&#xff08;北京&#xff09;软件有限公司成立于2024年&#xff0c;技术和研发团队均来自于一家具有近10年监控系统研发的企业。广目的技术团队一共实施了9家政府单位、1家股份制银行、1家芯片制造企业的数据中心监控预警项目。这11家政企单位由2家正部级、1家副部级、6家…...

每日八股文6.6

每日八股-6.6 Mysql1.怎么查看一条sql语句是否走了索引&#xff1f;2.能说说 MySQL 事务都有哪些关键特性吗&#xff1f;3.MySQL 是如何保证事务的原子性的&#xff1f;4.MySQL 是如何保证事务的隔离性的&#xff1f;5.能简单介绍一下 MVCC 吗&#xff1f;或者说&#xff0c;你…...

动静态库的使用(Linux下)

1.库 通俗来说&#xff0c;库就是现有的&#xff0c;可复用的代码&#xff0c;例如&#xff1a;在C/C语言编译时&#xff0c;就需要依赖相关的C/C标准库。本质上来说库是一种可执行代码的二进制形式&#xff0c;可以被操作系统载入内存执行。通常我们可以在windows下看到一些后…...

PostgreSQL17 编译安装+相关问题解决

更新时间&#xff1a;2025.6.6&#xff0c;当前最新稳定版本17.5&#xff0c;演示的是17.5&#xff0c;最新测试版本18beta1 演示系统&#xff1a;debian12 很多时候&#xff0c;只有编译安装才能用上最新的软件版本或指定的版本。这也是编译安装的意义。 一、编译安装 &…...

FFMPEG 提取视频中指定起始时间及结束时间的视频,给出ffmpeg 命令

以下是提取视频中指定起始时间及结束时间的 ffmpeg 命令示例: bash 复制 ffmpeg -i input.mp4 -ss 00:01:30.00 -to 00:05:00.00 -c copy output.mp4 其中,-i input.mp4 是指定要处理的输入视频文件为 “input.mp4”。 -ss 00:01:30.00 表示指定视频的起始时间为 1 分 30 …...

React 第五十六节 Router 中useSubmit的使用详解及注意事项

前言 useSubmit 是 React Router v6.4 引入的强大钩子&#xff0c;用于以编程方式提交表单数据。 它提供了对表单提交过程的精细控制&#xff0c;特别适合需要自定义提交行为或非标准表单场景的应用。 一、useSubmit 核心用途 编程式表单提交&#xff1a;不依赖 <form>…...

华为云学堂-云原生开发者认证课程列表

华为云学堂-云原生认证 云原生开发者认证的前5个课程...

Vue.js 组件:深入理解与实践

Vue.js 组件:深入理解与实践 引言 随着前端技术的不断发展,Vue.js 作为一种流行的前端框架,因其简洁、易学、高效的特点受到越来越多开发者的青睐。在Vue.js中,组件是构建用户界面的基石。本文将深入探讨Vue.js组件的概念、特性、创建方式以及在实际开发中的应用,帮助读…...

什么是强化学习:设置奖励函数最为loss, 监督学习:标签准确率作为loss

什么是强化学习:设置奖励函数最为loss, 监督学习:标签准确率作为loss 什么是强化学习:在复杂环境中自主探索,适用于序列决策 最大优势: 通过试错探索发现最优策略,适应环境动态变化,擅长解决需要长期规划和序列决策的问题。典型案例: 游戏AI(如AlphaGo/AlphaZero):…...

理解网络协议

1.查看网络配置 : ipconfig 2. ip地址 : ipv4(4字节, 32bit), ipv6, 用来标识主机的网络地址 3.端口号(0~65535) : 用来标识主机上的某个进程, 1 ~ 1024 知名端口号, 如果是服务端的话需要提供一个特定的端口号, 客户端的话是随机分配一个端口号 4.协议 : 简单来说就是接收数据…...

placeholder不显示and模板字符串无效

一、placeholder"请输入"不显示请输入&#xff1f; input框里写了placeholder为什么不显示呢&#xff1f; 检查代码&#xff0c;input是否有初始值 在 Vue.js 中&#xff0c;v-model 是双向绑定的语法糖&#xff0c;它会动态更新输入框的 value。如果绑定的数据有初…...

在MyBatis中设计SQL返回布尔值(Boolean)有几种常见方法

方案一&#xff1a;使用COUNT查询存在性&#xff08;推荐&#xff09; <select id"checkUserExists" resultType"_boolean">SELECT COUNT(*) > 0 FROM users WHERE username #{username} </select> ​​说明​​&#xff1a; MySQL中COU…...

全球知名具身智能/AI机器人实验室介绍之AI FACTORY基于慕尼黑工业大学

全球知名具身智能/AI机器人实验室介绍之AI FACTORY基于慕尼黑工业大学 TUM AI FACTORY&#xff0c;即KI.FABRIK&#xff0c;是德国慕尼黑工业大学&#xff08;TUM&#xff09;在巴伐利亚州推出的一个旗舰项目&#xff0c;旨在打造未来工厂&#xff0c;将传统工厂转变为由人工智…...

DASCTF

[DASCTF X 0psu3十一月挑战赛&#xff5c;越艰巨越狂热]EzPenetration Tip:数据库里的邮箱key已更改为管理员密码&#xff0c;拿到后可直接登录 打开靶机&#xff0c;用Wappalyzer分析网站&#xff0c;可以看到管理系统是Wordpress&#xff0c;因此可以尝试用WPSSCAN扫描公开…...

钉钉 - 机器人消息推送(签名版)

前言 在日常生活中&#xff0c;我们可能会遇到某些异常发生后需要紧急通知到群里&#xff0c;让相关人员看到紧急处理的事件触发机制。 消息群我采用的是钉钉推送&#xff0c;本文介绍了如何用php 推送钉钉机器人消息。 源码封装 <?php /*** 钉钉通知 - 签名版*/ class …...

Redux 实践与中间件应用

Redux 异步处理的挑战 Redux 核心设计是同步的、单向数据流&#xff0c;但现代应用中异步操作无处不在。Redux 中间件填补了这一缺口&#xff0c;专门解决异步流程管理、副作用隔离等复杂场景。 中间件架构原理 中间件位于 action 被发起之后、到达 reducer 之前&#xff0c…...

ModBus总线协议

一、知识点 1. 什么是Modbus协议&#xff1f; Modbus 是一种工业通信协议&#xff0c;最早由 Modicon 公司在1979年提出&#xff0c;目的是用于 PLC&#xff08;可编程逻辑控制器&#xff09;之间的数据通信。它是主从式通信&#xff0c;即一个主机&#xff08;主设备&#xf…...

【计算机网络】非阻塞IO——poll实现多路转接

&#x1f525;个人主页&#x1f525;&#xff1a;孤寂大仙V &#x1f308;收录专栏&#x1f308;&#xff1a;计算机网络 &#x1f339;往期回顾&#x1f339;&#xff1a;【计算机网络】非阻塞IO——select实现多路转接 &#x1f516;流水不争&#xff0c;争的是滔滔不息 一、…...

在.NET Core控制器中获取AJAX传递的Body参数

.Net Core是支持前后端不分离式的开发的&#xff0c;如果在原始系统中采用不分离式开发&#xff0c;后面需要在原系统中增加功能&#xff0c;并且新的服务采用其他语言开发&#xff0c;且系统原来功能保持原样&#xff0c;这样前端系统可以单独调用新开发的接口。 但是&#x…...

snprintf函数用法及注意事项详解

当 format 后没有可变参数&#xff08;即 ... 为空&#xff09;时&#xff0c;va_start 的行为和后续操作如下&#xff1a; 1. va_start 的行为 va_start 的核心任务是根据最后一个固定参数&#xff08;format&#xff09;的地址&#xff0c;计算可变参数列表的起始位置。即使…...

vue-20(Vuex 状态管理的最佳实践)

Vuex 状态管理的最佳实践 Vuex 是管理大型 Vue.js 应用状态的一个强大工具&#xff0c;但其有效性取决于其组织和维护的质量。管理不善的 Vuex 存储可能会变得难以控制、难以调试&#xff0c;并成为性能瓶颈。本课程深入探讨构建 Vuex 存储的最佳实践&#xff0c;重点关注可维…...

DAX权威指南8:DAX引擎与存储优化

文章目录 十七、DAX引擎17.1 DAX 引擎的体系结构17.1.1 表格模型的双引擎架构17.1.2 存储引擎的三种模式17.1.2.1 VertiPaq引擎17.1.2.2 DirectQuery 引擎17.1.2.3 对比与最佳实践 17.1.3 数据刷新 17.2 理解 VertiPaq 存储引擎17.2.1 列式数据库17.2.2 VertiPaq 压缩17.2.2.1 …...

智慧货运飞船多维度可视化管控系统

图扑搭建智慧货运飞船可视化系统&#xff0c;借数字孪生技术&#xff0c;高精度复刻货运飞船外观、结构与运行场景。整合多维度数据&#xff0c;实时呈现飞行状态、设备参数等信息&#xff0c;助力直观洞察货运飞船运行逻辑&#xff0c;为航天运维、任务推演及决策提供数字化支…...

电脑开不了机,主板显示67码解决过程

文章目录 现象分析内存条问题BIOS设置问题其它问题 解决清理内存条金手指所需工具操作步骤注意事项 电脑在运行过程中&#xff0c;显示内存不足&#xff0c;重启电脑却无法启动。 现象 System Initialization 主板风扇是转的&#xff0c;也有灯光显示&#xff0c;插上屏幕&am…...

Spring Boot 类加载机制深度解析

Spring Boot 类加载机制深度解析 前言 在 Java 应用开发中&#xff0c;类加载机制是一个重要且复杂的话题。Spring Boot 作为现代 Java 开发的主流框架&#xff0c;其类加载机制更是值得深入了解。本文将从基础概念到实际应用&#xff0c;全面解析 Spring Boot 的类加载机制。…...

Python 训练营打卡 Day 45

TensorBoard 简单来说&#xff0c;TensorBoard 是 TensorFlow 自带的一个「可视化工具」&#xff0c;就像给机器学习模型训练过程装了一个「监控屏幕」。你可以用它直观看到训练过程中的数据变化&#xff08;比如损失值、准确率&#xff09;、模型结构、数据分布等&#xff0c…...