设计模式背后的设计原则和思想
设计模式背后的设计原则和思想是一套指导我们如何设计高质量软件系统的准则和方法论。这些原则和思想不仅有助于提升软件的可维护性、可扩展性和可复用性,还能帮助开发团队更好地应对复杂多变的需求。以下是一些核心的设计原则和思想:
1. 设计原则
设计模式背后的设计原则主要包括但不限于以下几点:
单一职责原则
一个类应该仅有一个引起它变化的原因,即一个类应该负责一组相对独立的功能。这有助于降低类的复杂度,提高系统的可维护性。
在Java中实现单一职责原则
这个原则指出一个类应该仅负责一项职责。如果一个类承担了过多的职责,那么当这些职责中的一个发生变化时,就可能会影响到类中的其他职责,从而导致代码的脆弱性和难以维护。
要在Java中实践单一职责原则,你可以遵循以下几个步骤:
- 识别职责:
- 首先,仔细分析你的类和它的方法,确定每个类所承担的职责。这可能需要一些重构的工作,比如将大类的功能拆分成更小的类。
- 分离职责:一旦识别出类中的多个职责,就考虑将它们分离到不同的类中。每个新类应该只负责一个明确的职责。
- 定义接口:为每个职责定义一个清晰的接口。接口是类之间通信的契约,它可以帮助你保持类的职责清晰,并促进代码的解耦。
- 实现接口:让每个类实现它对应的接口,并确保每个类只实现它应该承担的职责。
- 重构和测试:在重构过程中,不要忘记进行充分的测试。确保重构后的代码仍然能够正确地工作,并且性能没有显著下降。
示例
假设你有一个
Employee类,它最初可能包含了处理员工信息、计算工资和打印日志等多种职责。按照单一职责原则,你可以将这个类拆分成多个类,每个类只负责一个职责。// EmployeeInfo 类负责处理员工信息 public class EmployeeInfo {private String name;private String id;// 构造方法、getter和setter省略public void setName(String name) {this.name = name;}public String getName() {return name;}// 其他与员工信息相关的方法 }// SalaryCalculator 类负责计算工资 public class SalaryCalculator {// 假设根据员工信息来计算工资public double calculateSalary(EmployeeInfo employeeInfo) {// 计算逻辑return 0.0; // 示例返回} }// LogPrinter 类负责打印日志 public class LogPrinter {public void printLog(String message) {// 打印逻辑System.out.println(message);} }在这个示例中,
EmployeeInfo、SalaryCalculator和LogPrinter每个类都只负责一个明确的职责。这样,当需要修改工资计算逻辑或日志打印方式时,你只需修改对应的类,而不会影响到其他职责的实现。这有助于保持代码的清晰和可维护性。
开闭原则
软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。这意味着我们应该通过添加新的代码来扩展软件的功能,而不是修改现有的代码。
在Java中实现开闭原则
在Java中,实现开闭原则(Open-Closed Principle, OCP)是面向对象设计中的一个重要概念。开闭原则强调软件实体(类、模块、函数等)应该对扩展开放,对修改关闭。这意味着当软件需要增加新功能时,应该通过扩展已有的代码来实现,而不是修改已有的代码。
要在Java中实现开闭原则,你可以遵循以下几个步骤和策略:
抽象化:首先,你需要识别出软件系统中可能会变化的部分,并将这些部分抽象化。这通常意味着定义一些接口或抽象类,它们声明了稳定的服务或操作,但不提供具体的实现。
依赖抽象:在软件的其他部分,应该依赖于这些接口或抽象类的引用,而不是具体的实现类。这样做的好处是,当需要改变实现时,只需要替换实现类,而不需要修改依赖于接口的代码。
使用策略模式:策略模式是实现开闭原则的一种常见方式。通过策略模式,你可以定义一系列的算法,并将每一个算法封装起来,使它们可以互相替换。策略模式让算法的变化独立于使用算法的客户。
组合优于继承:在Java中,继承是一种强大的功能,但它也可能导致类之间的紧密耦合。如果可能的话,使用组合(即对象持有其他对象的引用)来代替继承,这样可以更容易地扩展和修改类。
避免使用硬编码:硬编码的值或依赖关系会限制你的代码的灵活性。尽量使用配置文件、依赖注入或其他机制来管理这些值或依赖关系,以便在需要时能够轻松地进行更改。
定期重构:随着软件的发展,你可能会发现一些代码不再符合开闭原则。在这种情况下,不要害怕进行重构。重构是一种有目的地改进现有代码结构的过程,它可以帮助你保持代码的灵活性和可维护性。
Java示例
// 定义一个接口,声明稳定的服务 interface Logger {void log(String message); }// 实现接口的类之一 class ConsoleLogger implements Logger {public void log(String message) {System.out.println(message);} }// 实现接口的类之二(未来可能添加更多实现) class FileLogger implements Logger {public void log(String message) {// 假设这里将消息写入文件System.out.println("Logging to file: " + message);} }// 使用接口的地方 class Application {private Logger logger;public Application(Logger logger) {this.logger = logger;}public void execute() {// 执行一些操作String result = "Operation completed successfully";logger.log(result); // 依赖于接口,不依赖于具体实现} }// 客户端代码 public class Main {public static void main(String[] args) {Application app = new Application(new ConsoleLogger());app.execute(); // 输出到控制台// 如果需要,可以轻松地将日志输出改为文件// Application app2 = new Application(new FileLogger());// app2.execute(); // 输出到文件} }在这个示例中,
Logger接口定义了一个稳定的服务,而ConsoleLogger和FileLogger是实现这个服务的具体类。Application类依赖于Logger接口,而不是具体的实现类,因此它可以在不修改自身代码的情况下,轻松地切换到不同的日志实现。这就体现了开闭原则的精神:对扩展开放,对修改关闭。
里氏替换原则
所有引用基类(父类)的地方必须能透明地使用其子类的对象。这保证了子类可以替换掉父类而不会引起程序的错误。
在Java中实现里氏替换原则
在Java中,实现里氏替换原则(Liskov Substitution Principle, LSP)是面向对象设计中的一个关键概念。这个原则由芭芭拉·里斯科夫(Barbara Liskov)在1987年提出,它强调子类型必须能够替换掉它们的基类型,而程序的行为不会因此发生变化。换句话说,软件实体(类、模块、函数等)在使用基类的引用时,必须能够透明地使用其子类的对象,而不需要知道具体的子类类型。
要在Java中实现里氏替换原则,你可以遵循以下几个步骤和原则:
- 确保子类遵循基类的契约:
- 子类应该遵循基类所定义的接口规范,包括方法的行为和前置条件、后置条件。
- 子类不应该添加基类中没有的前置条件,也不应该改变基类方法的后置条件。
- 避免重写方法时改变方法的行为:
- 当子类重写父类的方法时,应该确保方法的行为在逻辑上是一致的,或者至少是预期内的。
- 子类不应该违反基类方法的语义,除非这是通过明确的接口变更来声明的。
- 使用抽象类和接口来定义契约:
- 通过定义清晰的接口或抽象类,你可以为子类设定一个明确的契约。
- 子类必须实现这些接口或继承这些抽象类,从而确保它们遵循共同的规则。
- 注意返回类型的协变:
- 如果基类方法返回一个对象,子类在重写该方法时应该返回该对象或其子类的对象。
- 这有助于保持类型系统的安全性和灵活性。
- 使用多态和依赖注入:
- 通过多态和依赖注入,你可以在运行时动态地替换基类的实现,而不需要修改依赖于该基类的代码。
- 这使得系统更加灵活,并且易于扩展和维护。
- 测试和验证:
- 编写单元测试来验证子类是否可以正确地替换基类,并且不会破坏程序的正确性。
- 使用持续集成(CI)和自动化测试来确保在更改代码时不会违反里氏替换原则。
- 避免过度设计:
- 虽然里氏替换原则是一个重要的设计原则,但也要注意不要过度设计。
- 有时候,简单的继承关系就足以满足需求,而不必强制遵循所有的设计原则。
在Java实践中,遵循里氏替换原则可以帮助你设计出更加灵活、可维护和可扩展的软件系统。然而,这也需要你在设计过程中不断地思考和权衡,以确保你的设计既符合原则又实用。
示例
在Java中实现里氏替换原则,关键在于确保子类能够无缝地替换掉其父类,而不会破坏原有程序的正确性。这里,我将提供一个简单的示例来展示如何在实际代码中应用里氏替换原则。
首先,我们定义一个基类(或接口),然后创建一个或多个子类来实现或继承这个基类。在这个例子中,我将使用抽象类和继承的方式。
// 基类(抽象类) abstract class Shape {// 定义一个抽象方法,所有子类都需要实现这个方法abstract void draw(); }// 子类1,实现Shape接口 class Rectangle extends Shape {@Overridevoid draw() {System.out.println("Inside Rectangle::draw() method.");} }// 子类2,也实现Shape接口 class Circle extends Shape {@Overridevoid draw() {System.out.println("Inside Circle::draw() method.");} }// 使用Shape类的客户端类 class TestShape {// 使用Shape类型的引用指向子类对象public void drawAllShapes(Shape s) {s.draw(); // 在这里,里氏替换原则被应用}// 测试方法public static void main(String[] args) {TestShape testShape = new TestShape();// 创建对象Rectangle rectangle = new Rectangle();Circle circle = new Circle();// 调用drawAllShapes方法来绘制RectangletestShape.drawAllShapes(rectangle);// 调用drawAllShapes方法来绘制Circle// 注意:这里Rectangle被Circle无缝替换,程序的行为仍然是正确的testShape.drawAllShapes(circle);} }在这个例子中,
Shape是一个抽象类,它定义了一个draw方法。Rectangle和Circle是Shape的子类,它们各自实现了draw方法。在TestShape类中,我们定义了一个drawAllShapes方法,它接受一个Shape类型的参数。由于Rectangle和Circle都是Shape的子类,因此我们可以将它们的实例传递给drawAllShapes方法,并且根据里氏替换原则,这些方法调用将按照预期工作,而不会破坏程序的正确性。这个示例展示了如何在Java中实现里氏替换原则,即子类对象可以在不修改原有代码的情况下替换掉父类对象,而程序的行为保持不变。
依赖倒置原则
高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。这有助于减少类之间的耦合度。
在Java中实现依赖倒置原则
在Java中实现依赖倒置原则(Dependency Inversion Principle, DIP)是一种设计思想,旨在减少类之间的耦合度,提高系统的灵活性和可维护性。依赖倒置原则强调两个核心点:
- 高层模块不应该依赖低层模块,两者都应该依赖其抽象:这意味着你应该定义接口或抽象类来声明服务的契约,而不是直接依赖于具体的实现类。高层模块(通常是业务逻辑层)应该通过接口或抽象类来与低层模块(如数据访问层、服务层等)进行交互。
- 抽象不应该依赖细节,细节应该依赖抽象:这意味着接口或抽象类不应该依赖于具体的实现细节,而是由具体的实现类来遵循接口或抽象类所定义的契约。这样做的好处是,当需要改变实现时,只需要替换具体的实现类,而不需要修改依赖于接口或抽象类的高层模块。
在Java中实现依赖倒置原则的具体步骤可以包括:
定义接口或抽象类:首先,你需要定义一系列接口或抽象类,这些接口或抽象类声明了服务或操作的契约。这些接口或抽象类不包含具体的实现,而是由子类或实现类来提供具体的实现。
编写实现类:然后,你编写具体的实现类来实现这些接口或继承这些抽象类。这些实现类包含了服务的具体实现细节。
高层模块依赖于接口或抽象类:在你的高层模块中,你应该通过接口或抽象类的引用来与低层模块进行交互。这样做的好处是,当需要改变低层模块的实现时,你不需要修改高层模块的代码,只需要替换掉实现类即可。
使用依赖注入:依赖注入是一种常用的技术,它可以帮助你实现依赖倒置原则。通过依赖注入,你可以在运行时动态地将依赖关系注入到对象中,而不是在编译时静态地定义它们。这可以提高代码的灵活性和可测试性。
示例
// 定义一个接口 interface Logger {void log(String message); }// 实现接口的具体类 class ConsoleLogger implements Logger {public void log(String message) {System.out.println(message);} }// 另一个实现接口的具体类 class FileLogger implements Logger {public void log(String message) {// 假设这里将消息写入文件System.out.println("Logging to file: " + message);} }// 高层模块,依赖于Logger接口 class Application {private Logger logger;// 通过构造函数注入依赖public Application(Logger logger) {this.logger = logger;}public void execute() {// 执行一些操作String result = "Operation completed successfully";logger.log(result); // 依赖于接口,而不是具体的实现类} }// 客户端代码 public class Main {public static void main(String[] args) {Application app = new Application(new ConsoleLogger());app.execute(); // 输出到控制台// 如果需要,可以轻松地将日志输出改为文件// Application app2 = new Application(new FileLogger());// app2.execute(); // 输出到文件} }在这个示例中,
Logger接口定义了日志服务的契约,ConsoleLogger和FileLogger是实现这个接口的具体类。Application类是一个高层模块,它依赖于Logger接口而不是具体的实现类。这样做的好处是,当需要改变日志的实现方式时(比如从控制台输出改为文件输出),你不需要修改Application类的代码,只需要更换实现类即可。
接口隔离原则
使用多个专门的接口比使用单一的总接口要好。这避免了接口污染和不必要的依赖。
在Java中实现接口隔离原则
1. 定义小而专的接口
- 避免胖接口:胖接口指的是包含很多方法的接口,这些方法可能被不同的类以不同的方式使用。相反,你应该将接口拆分成更小的、更具体的接口,每个接口只包含一组相关的方法。
2. 根据使用场景定义接口
- 按角色定义接口:考虑不同的客户端(或角色)可能需要不同的接口方法。为每个角色定义一个接口,而不是将所有方法都放在一个接口中。
3. 使用接口组合代替接口继承
- 组合优于继承:在Java中,接口之间可以通过继承来共享方法。然而,如果一个接口继承了另一个接口,它可能会被迫包含一些不需要的方法。使用接口组合(即一个类实现多个接口)可以更加灵活地定义所需的行为。
4. 使用适配器模式或代理模式来适配旧系统
- 适配旧代码:如果你正在处理一个旧的代码库,其中包含了胖接口,你可以使用适配器模式或代理模式来封装旧接口,并提供更小的、更具体的接口给新的客户端代码使用。
5. 定期检查接口
- 重构接口:随着系统的发展,接口的使用方式可能会发生变化。定期审查接口,并根据需要进行重构,以确保它们仍然保持小而专。
示例
假设你有一个处理用户信息的系统,原本有一个
UserInfoManager接口,它包含了所有与用户信息相关的操作(如获取用户信息、更新用户信息、删除用户等)。现在,根据接口隔离原则,你可以将其拆分为几个更小的接口:public interface UserInfoRetriever {UserInfo getUserInfo(String userId); }public interface UserInfoUpdater {void updateUserInfo(String userId, UserInfo newInfo); }public interface UserInfoDeleter {void deleteUserInfo(String userId); }// 然后,你的类可以只实现它需要的接口 public class UserService implements UserInfoRetriever, UserInfoUpdater {// 实现getUserInfo和updateUserInfo方法 }通过这种方式,
UserService类只依赖于它实际需要的接口,而不是一个包含所有可能方法的庞大接口。这有助于减少类之间的耦合,并提高系统的灵活性和可维护性。
迪米特法则
一个对象应该对其他对象保持最少的了解,即尽量减少类之间的交互。这有助于降低系统的复杂度。
在Java中实现迪米特法则
在Java中实现迪米特法则(Demeter's Law),也称为最少知识原则(Law of Least Knowledge)或最少通信原则(Principle of Least Communication),主要是为了降低模块之间的耦合度,提高系统的可维护性和可测试性。迪米特法则强调一个软件实体应当尽可能少地与其他实体发生相互作用。
在Java中,实现迪米特法则可以遵循以下几个步骤:
- 定义明确的接口:首先,为系统中的各个模块定义清晰的接口。这些接口应该明确说明模块之间可以如何交互,而不是暴露内部实现细节。
- 使用中介者模式:如果多个类需要相互通信,但又不想直接相互依赖,可以考虑使用中介者模式。中介者模式可以集中控制这些类之间的交互,从而减少它们之间的直接依赖。
- 限制访问级别:在Java中,你可以使用访问修饰符(如
private、protected、default(包级私有)和public)来限制类成员(包括属性和方法)的可见性。通过只暴露必要的公共接口,并将其他所有内容设为私有或受保护的,可以减少外部类对内部实现的依赖。- 使用依赖注入:依赖注入是一种将依赖项(即其他类的实例)提供给类的方法,而不是让类自己创建它们。通过依赖注入,你可以减少类之间的直接依赖,并在运行时动态地替换依赖项。这有助于实现迪米特法则,因为你可以通过配置文件或注解来指定依赖项,而不需要在代码中硬编码它们。
- 避免全局变量和公共静态变量:全局变量和公共静态变量会导致类之间的紧密耦合,因为它们可以在任何地方被访问和修改。尽量避免使用它们,而是使用私有字段和公共方法来封装数据和行为。
- 编写单元测试:编写单元测试可以帮助你确保在更改代码时不会破坏现有的依赖关系。通过单元测试,你可以验证类之间的交互是否符合预期,并在发现问题时立即进行修复。
- 重构:随着系统的发展,你可能会发现一些类违反了迪米特法则。在这种情况下,不要害怕进行重构。重构是改进代码结构和降低耦合度的关键过程。
Java示例
// 定义一个接口 interface MessageSender {void sendMessage(String message); }// 实现接口的具体类 class EmailSender implements MessageSender {public void sendMessage(String message) {// 发送电子邮件的逻辑System.out.println("Sending email: " + message);} }// 另一个实现接口的具体类 class SMSSender implements MessageSender {public void sendMessage(String message) {// 发送短信的逻辑System.out.println("Sending SMS: " + message);} }// 使用接口的类 class NotificationService {private MessageSender sender;// 通过构造函数注入依赖public NotificationService(MessageSender sender) {this.sender = sender;}public void notify(String message) {sender.sendMessage(message); // 依赖于接口,而不是具体的实现类} }// 客户端代码 public class Main {public static void main(String[] args) {NotificationService service = new NotificationService(new EmailSender());service.notify("Hello, this is an email notification.");// 如果需要,可以轻松地将通知方式改为短信// NotificationService service2 = new NotificationService(new SMSSender());// service2.notify("Hello, this is an SMS notification.");} }在这个示例中,
NotificationService类依赖于MessageSender接口,而不是具体的实现类(如EmailSender或SMSSender)。这使得NotificationService类更加灵活,因为它可以在不修改自身代码的情况下与不同的消息发送实现进行交互。这符合迪米特法则的精神,即尽量减少类之间的直接依赖。
2. 设计思想
设计模式背后的设计思想主要体现在以下几个方面:
- 面向对象:面向对象编程(OOP)是设计模式的基础。它提供了封装、继承、多态等特性,使得我们可以更好地组织和管理代码。通过面向对象的思想,我们可以将复杂的系统分解为一系列相互协作的对象,从而降低系统的复杂度。
- 模块化:将系统划分为一系列相对独立的模块,每个模块都负责一组特定的功能。模块化设计有助于降低系统各部分之间的耦合度,提高系统的可维护性和可扩展性。
- 抽象与封装:通过抽象和封装,我们可以隐藏实现细节,只暴露必要的接口给外部使用。这有助于降低系统的复杂度,提高代码的可读性和可维护性。
- 复用与组合:设计模式强调代码的复用和组合。通过复用已有的设计模式或组件,我们可以快速搭建出高质量的软件系统。同时,通过组合不同的设计模式或组件,我们可以实现更复杂的功能。
相关文章:
设计模式背后的设计原则和思想
设计模式背后的设计原则和思想是一套指导我们如何设计高质量软件系统的准则和方法论。这些原则和思想不仅有助于提升软件的可维护性、可扩展性和可复用性,还能帮助开发团队更好地应对复杂多变的需求。以下是一些核心的设计原则和思想: 1. 设计原则 设计…...
项目总体框架
一.后端(包装servlet) 使用BaseServlet进行请求的初步处理(利用继承进行执行这个) 在BaseServlet中 处理请求的类型找到对象的方法,并使用注解找到参数名,执行参数自动注入。 package com.csdn.controlle…...
k8s Prometheus
一、部署 Prometheus kubectl create ns kube-ops# 创建 prometheus-cm.yaml apiVersion: v1 kind: ConfigMap metadata:name: prometheus-confignamespace: kube-ops data:prometheus.yml: |global:scrape_interval: 15s # 表示 prometheus 抓取指标数据的频率,默…...
glsl着色器学习(九)屏幕像素空间和设置颜色
在上一篇文章中,使用的是裁剪空间进行绘制,这篇文章使用屏幕像素空间的坐标进行绘制。 上一篇的顶点着色器大概是这样子的 回归一下顶点着色的主要任务: 通常情况下,顶点着色器会进行一系列的矩阵变换操作,将输入的顶…...
前端框架有哪些?
前言 用户体验是每个开发网站的企业中的重中之重。无论后台有多方面的操作和功能,用户的视图和体验都必须是无缝的最友好的。这需要使用前端框架来简化交互式、以用户为中心的网站的开发。 前端框架是一种用于简化Web开发的工具,它提供了一套预定义的代…...
分类预测|基于黑翅鸢优化轻量级梯度提升机算法数据预测Matlab程序BKA-LightGBM多特征输入多类别输出 含对比
分类预测|基于黑翅鸢优化轻量级梯度提升机算法数据预测Matlab程序BKA-LightGBM多特征输入多类别输出 含对比 文章目录 一、基本原理BKA(Black Kite Algorithm)的原理LightGBM分类预测模型的原理BKA与LightGBM的模型流程总结 二、实验结果三、核心代码四、…...
利用大模型实时提取和检索多模态数据探索-利用 Indexify 进行文档分析
概览 传统的文本提取方法常常无法理解非结构化内容,因此提取数据的数据往往是错误的。本文将探讨使用 Indexify,一个用于实时多模态数据提取的开源框架,来更好地分析pdf等非结构化文件。我将介绍如何设置 Indexify,包括服务器设置…...
函数式接口实现策略模式
函数式接口实现策略模式 1.案例背景 我们在日常开发中,大多会写if、else if、else 这样的代码,但条件太多时,往往嵌套无数层if else,阅读性很差,比如如下案例,统计学生的数学课程的成绩: 90-100分&#…...
鸿蒙Next-拉起支付宝的三种方式——教程
鸿蒙Next-拉起支付宝的三种方式——教程 鸿蒙Next系统即将上线,应用市场逐渐丰富、很多APP都准备接入支付宝做支付功能,目前来说有三种方式拉起支付宝:通过支付宝SDK拉起、使用OpenLink拉起、传入支付宝包名使用startAbility拉起。以上的三种…...
Vue.js 组件化开发:父子组件通信与组件注册详解
Vue.js 组件化开发:父子组件通信与组件注册详解 简介: 在 Vue.js 的开发中,组件是构建应用的重要基础。掌握组件的创建与使用,尤其是父子组件的通信和组件的注册与命名,是开发中不可或缺的技能。本文将详细探讨这些内容…...
【HTTP、Web常用协议等等】前端八股文面试题
HTTP、Web常用协议等等 更新日志 2024年9月5日 —— 什么情况下会导致浏览器内存泄漏? 文章目录 HTTP、Web常用协议等等更新日志1. 网络请求的状态码有哪些?1)1xx 信息性状态码2)2xx 成功状态码3)3xx 重定向状态码4&…...
Datawhale x李宏毅苹果书AI夏令营深度学习详解进阶Task03
在深度学习中,批量归一化(Batch Normalization,BN)技术是一种重要的优化方法,它可以有效地改善模型的训练效果。本文将详细讨论批量归一化的原理、实现方式、在神经网络中的应用,以及如何选择合适的损失函数…...
【机器学习】任务三:基于逻辑回归与线性回归的鸢尾花分类与波士顿房价预测分析
目录 1.目的和要求 1.1 掌握回归分析的概念和使用场景 1.2 掌握机器学习回归分析进行数据预测的有效方法 1.3 掌握特征重要性分析、特征选择和模型优化的方法 2.波士顿房价预测与特征分析 2.1第一步:导入所需的模块和包 2.2 第二步:加载波士顿房价…...
【操作系统存储篇】Linux文件基本操作
目录 一、Linux目录 二、Linux文件的常用操作 三、Linux文件类型 一、Linux目录 Linux有很多目录,Linux一切皆是文件,包括进程、设备等。 相对路径:相对于当前的操作目录,文件位于哪个目录。 绝对路径 :从根目录开…...
C++ | Leetcode C++题解之第387题字符串中的第一个唯一字符
题目: 题解: class Solution { public:int firstUniqChar(string s) {unordered_map<char, int> position;queue<pair<char, int>> q;int n s.size();for (int i 0; i < n; i) {if (!position.count(s[i])) {position[s[i]] i;…...
数学建模--皮尔逊相关系数、斯皮尔曼相关系数
目录 1.总体的皮尔逊相关系数 2.样本的皮尔逊相关系数 3.对于皮尔逊相关系数的认识 4.描述性统计以及corr函数 编辑 5.数据导入实际操作 6.引入假设性检验 6.1简单认识 6.2具体步骤 7.p值判断法 8.检验正态分布 8.1jb检验 8.2威尔克检验:针对于p值进行…...
DAY87 APP 攻防-安卓逆向篇Smail 语法反编译签名重打包Activity 周期Hook 模块
1、APK 逆向-数据修改-结构&格式 2、APK 逆向-逻辑修改-Smail 语法 3、APK 逆向-视图修改-Activity&Xml #章节点: 1、APP 资产-内在提取&外在抓包 2、APP 逆向-反编译&删验证&重打包 3、APP 安全-存储&服务&组件&注册等 演示案例&a…...
jenkins 工具使用
使用方式 替代手动,自动化拉取、集成、构建、测试;是CI/CD持续集成、持续部署主流开发模式中重要的环节;必须组件 jenkins-gitlab,代码公共仓库服务器(至少6G内存);jenkins-server,…...
使用C语言实现字符推箱子游戏
使用C语言实现字符推箱子游戏 推箱子(Sokoban)是一款经典的益智游戏,玩家通过移动角色将箱子推到目标位置。本文将带你一步步用C语言实现一个简单的字符版本的推箱子游戏。 游戏规则 玩家只能推箱子,不能拉箱子。只能将箱子推到…...
用SpringBoot API实现识别pdf文件是否含有表格
要使用Spring Boot API 实现一个识别 PDF 文件是否含有表格的功能,你可以结合 PDF 解析库(如 Apache PDFBox)来解析 PDF 文件内容,并通过分析文本或线条来判断 PDF 是否包含表格。然后使用 Spring Boot 提供的 REST API 来实现上传…...
国防科技大学计算机基础课程笔记02信息编码
1.机内码和国标码 国标码就是我们非常熟悉的这个GB2312,但是因为都是16进制,因此这个了16进制的数据既可以翻译成为这个机器码,也可以翻译成为这个国标码,所以这个时候很容易会出现这个歧义的情况; 因此,我们的这个国…...
如何在看板中体现优先级变化
在看板中有效体现优先级变化的关键措施包括:采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中,设置任务排序规则尤其重要,因为它让看板视觉上直观地体…...
学习STC51单片机31(芯片为STC89C52RCRC)OLED显示屏1
每日一言 生活的美好,总是藏在那些你咬牙坚持的日子里。 硬件:OLED 以后要用到OLED的时候找到这个文件 OLED的设备地址 SSD1306"SSD" 是品牌缩写,"1306" 是产品编号。 驱动 OLED 屏幕的 IIC 总线数据传输格式 示意图 …...
C++ 求圆面积的程序(Program to find area of a circle)
给定半径r,求圆的面积。圆的面积应精确到小数点后5位。 例子: 输入:r 5 输出:78.53982 解释:由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982,因为我们只保留小数点后 5 位数字。 输…...
根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:
根据万维钢精英日课6的内容,使用AI(2025)可以参考以下方法: 四个洞见 模型已经比人聪明:以ChatGPT o3为代表的AI非常强大,能运用高级理论解释道理、引用最新学术论文,生成对顶尖科学家都有用的…...
OpenLayers 分屏对比(地图联动)
注:当前使用的是 ol 5.3.0 版本,天地图使用的key请到天地图官网申请,并替换为自己的key 地图分屏对比在WebGIS开发中是很常见的功能,和卷帘图层不一样的是,分屏对比是在各个地图中添加相同或者不同的图层进行对比查看。…...
Element Plus 表单(el-form)中关于正整数输入的校验规则
目录 1 单个正整数输入1.1 模板1.2 校验规则 2 两个正整数输入(联动)2.1 模板2.2 校验规则2.3 CSS 1 单个正整数输入 1.1 模板 <el-formref"formRef":model"formData":rules"formRules"label-width"150px"…...
学习STC51单片机32(芯片为STC89C52RCRC)OLED显示屏2
每日一言 今天的每一份坚持,都是在为未来积攒底气。 案例:OLED显示一个A 这边观察到一个点,怎么雪花了就是都是乱七八糟的占满了屏幕。。 解释 : 如果代码里信号切换太快(比如 SDA 刚变,SCL 立刻变&#…...
2023赣州旅游投资集团
单选题 1.“不登高山,不知天之高也;不临深溪,不知地之厚也。”这句话说明_____。 A、人的意识具有创造性 B、人的认识是独立于实践之外的 C、实践在认识过程中具有决定作用 D、人的一切知识都是从直接经验中获得的 参考答案: C 本题解…...
音视频——I2S 协议详解
I2S 协议详解 I2S (Inter-IC Sound) 协议是一种串行总线协议,专门用于在数字音频设备之间传输数字音频数据。它由飞利浦(Philips)公司开发,以其简单、高效和广泛的兼容性而闻名。 1. 信号线 I2S 协议通常使用三根或四根信号线&a…...
